11import abc
2+ import enum
23import functools
34import uuid
45
5- from viur import toolkit
6- from viur .core import Module , translate , utils
6+ from viur .core import CallDeferred , Module , current , db , translate , utils
77from viur .core .prototypes .instanced_module import InstancedModule
88from viur .core .skeleton import SkeletonInstance
9+
10+ from viur import toolkit
911from viur .shop .skeletons .order import OrderSkel
1012from ..types import *
1113
@@ -58,10 +60,14 @@ def description(self) -> translate:
5860 """Define the description of the payment provider"""
5961 return translate (f"viur.shop.payment_provider.{ self .name } .descr" , self .name )
6062
63+ # --- Internal Checks & Actions during the payment flow -------------------
64+ # --- (controlled by the order module) ------------------------------------
65+
6166 def is_available (
6267 self : t .Self ,
6368 order_skel : SkeletonInstance_T [OrderSkel ] | None ,
6469 ) -> bool :
70+ """Decide whether the payment provider is available."""
6571 return True
6672
6773 def can_checkout (
@@ -113,25 +119,52 @@ def check_payment_state(
113119 """
114120 ...
115121
122+ @CallDeferred
123+ # @log_unzer_error
124+ def check_payment_deferred (self , order_key : db .Key ) -> None :
125+ """Check the status for a payment deferred"""
126+ logger .debug (f"Checking payment for { order_key = !r} deferred" )
127+ order_skel = self .shop .order .skel ().read (order_key )
128+ logger .debug (f"Checking payment for { order_skel = !r} deferred" )
129+ # TODO: duplicate check / code?
130+ is_paid , payment = self .check_payment_state (order_skel )
131+ if is_paid and order_skel ["is_paid" ]:
132+ logger .info (f'Order { order_skel ["key" ]!r} already marked as paid. Nothing to do.' )
133+ elif is_paid :
134+ logger .info (f'Mark order { order_skel ["key" ]!r} as paid' )
135+ self .shop .order .set_paid (order_skel )
136+ else :
137+ logger .info (f'Order { order_skel ["key" ]!r} is not paid' )
138+
139+ # --- API Endpoints ------------------------------------------------------
140+
116141 @abc .abstractmethod
117142 # @exposed
118143 def return_handler (self ):
144+ """Frontend Endpoint where the might be redirected to by the payment provider during the payment flow"""
119145 ...
120146
121147 @abc .abstractmethod
122148 # @exposed
123149 def webhook (self ):
150+ """API Endpoint (Webhook) to listen for events from payment provider"""
124151 ...
125152
126153 @abc .abstractmethod
127154 # @exposed
128155 def get_debug_information (self ):
156+ """Provide information about the payment of an order.
157+
158+ Only for debugging purposes. It's not an API endpoint.
159+ """
129160 ...
130161
162+ # --- utils ---------------------------------------------------------------
163+
131164 def _append_payment_to_order_skel (
132165 self ,
133166 order_skel : SkeletonInstance_T [OrderSkel ],
134- payment : dict [ str , t . Any ] | None = None ,
167+ payment : PaymentTransactionSpecific | None = None ,
135168 ) -> SkeletonInstance_T [OrderSkel ]:
136169 """Append payment data to an order
137170
@@ -147,8 +180,10 @@ def set_payment(skel: SkeletonInstance):
147180 "payment_provider" : self .name ,
148181 "creationdate" : utils .utcNow ().isoformat (),
149182 "uuid" : str (uuid .uuid4 ()),
183+ "client_ip" : current .request .get ().request .client_addr ,
184+ "user_agent" : current .request .get ().request .user_agent ,
150185 }
151- | (payment or {})
186+ | (payment or {}) # type: PaymentTransaction
152187 )
153188
154189 order_skel = toolkit .set_status (
@@ -162,7 +197,7 @@ def serialize_for_api(
162197 self ,
163198 order_skel : SkeletonInstance_T [OrderSkel ] | None ,
164199 ) -> PaymentProviderResult :
165- """Serialize this Payment Provder for the API
200+ """Serialize this Payment Provider for the API
166201
167202 Used by :meth:`Order.get_payment_providers` and :meth:`Order.payment_providers_list`
168203 Can be subclasses to expose more information via API.
@@ -174,5 +209,17 @@ def serialize_for_api(
174209 is_available = self .is_available (order_skel ),
175210 )
176211
212+ @classmethod
213+ def model_to_dict (cls , obj : t .Any ) -> t .Any :
214+ """Convert any nested model to a JSON-compatible representation
215+ """
216+ if isinstance (obj , dict ):
217+ return {k : cls .model_to_dict (v ) for k , v in obj .items ()}
218+ elif isinstance (obj , list | tuple ):
219+ return [cls .model_to_dict (v ) for v in obj ]
220+ elif isinstance (obj , enum .Enum ):
221+ return f"{ obj !r} "
222+ return obj
223+
177224
178225PaymentProviderAbstract .html = True
0 commit comments