Skip to content

Commit b1c16e9

Browse files
committed
more paypal
1 parent d61b341 commit b1c16e9

File tree

9 files changed

+58
-30
lines changed

9 files changed

+58
-30
lines changed

src/viur/shop/globals.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
"""
2+
Global constants and settings for the viur-shop.
3+
"""
4+
15
import logging
26
import typing as t
37

src/viur/shop/payment_providers/abstract.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import abc
2+
import enum
23
import functools
34
import uuid
45

@@ -59,10 +60,14 @@ def description(self) -> translate:
5960
"""Define the description of the payment provider"""
6061
return translate(f"viur.shop.payment_provider.{self.name}.descr", self.name)
6162

63+
# --- Internal Checks & Actions during the payment flow -------------------
64+
# --- (controlled by the order module) ------------------------------------
65+
6266
def is_available(
6367
self: t.Self,
6468
order_skel: SkeletonInstance_T[OrderSkel] | None,
6569
) -> bool:
70+
"""Decide whether the payment provider is available."""
6671
return True
6772

6873
def can_checkout(
@@ -131,21 +136,31 @@ def check_payment_deferred(self, order_key: db.Key) -> None:
131136
else:
132137
logger.info(f'Order {order_skel["key"]!r} is not paid')
133138

139+
# --- API Endpoints ------------------------------------------------------
140+
134141
@abc.abstractmethod
135142
# @exposed
136143
def return_handler(self):
144+
"""Frontend Endpoint where the might be redirected to by the payment provider during the payment flow"""
137145
...
138146

139147
@abc.abstractmethod
140148
# @exposed
141149
def webhook(self):
150+
"""API Endpoint (Webhook) to listen for events from payment provider"""
142151
...
143152

144153
@abc.abstractmethod
145154
# @exposed
146155
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+
"""
147160
...
148161

162+
# --- utils ---------------------------------------------------------------
163+
149164
def _append_payment_to_order_skel(
150165
self,
151166
order_skel: SkeletonInstance_T[OrderSkel],
@@ -194,5 +209,17 @@ def serialize_for_api(
194209
is_available=self.is_available(order_skel),
195210
)
196211

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+
197224

198225
PaymentProviderAbstract.html = True

src/viur/shop/payment_providers/invoice.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from viur.core import errors, exposed
44
from viur.core.skeleton import SkeletonInstance
5+
56
from . import PaymentProviderAbstract
67
from ..globals import SHOP_LOGGER
78
from ..skeletons import OrderSkel

src/viur/shop/payment_providers/paypal_checkout.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import enum
21
import json
32
import logging
43
import 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)

src/viur/shop/payment_providers/prepayment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import typing as t
22

33
from deprecated.sphinx import deprecated
4-
54
from viur.core import errors, exposed
65
from viur.core.skeleton import SkeletonInstance
6+
77
from . import PaymentProviderAbstract
88
from ..globals import SHOP_LOGGER
99
from ..skeletons import OrderSkel

src/viur/shop/payment_providers/unzer_abstract.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import abc
2-
import enum
32
import functools
43
import json
54
import typing as t # noqa
@@ -150,6 +149,8 @@ def sandbox(self) -> bool:
150149
return self._sandbox()
151150
return self._sandbox
152151

152+
# --- Internal Checks & Actions during the payment flow -------------------
153+
153154
def can_checkout(
154155
self,
155156
order_skel: SkeletonInstance,
@@ -310,6 +311,8 @@ def check_payment_state(
310311

311312
return False, payment_results
312313

314+
# --- API Endpoints ------------------------------------------------------
315+
313316
@exposed
314317
@log_unzer_error
315318
@error_handler
@@ -528,14 +531,8 @@ def shop_salutation_to_unzer_salutation(
528531
}.get(salutation, UnzerSalutation.UNKNOWN)
529532

530533
@classmethod
531-
def model_to_dict(cls, obj):
534+
def model_to_dict(cls, obj: t.Any) -> t.Any:
532535
"""Convert any nested unzer model to dict representation"""
533536
if isinstance(obj, BaseModel):
534537
obj = dict(obj) # Convert to dict first, then process recursively
535-
if isinstance(obj, dict):
536-
return {k: cls.model_to_dict(v) for k, v in obj.items()}
537-
elif isinstance(obj, list | tuple):
538-
return [cls.model_to_dict(v) for v in obj]
539-
elif isinstance(obj, enum.Enum):
540-
return f"{obj!r}"
541-
return obj
538+
return super().model_to_dict(obj)

src/viur/shop/payment_providers/unzer_applepay.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import unzer
44
from unzer.model import PaymentType
5-
65
from viur.core.skeleton import SkeletonInstance
6+
77
from .unzer_abstract import UnzerAbstract
88
from ..globals import SHOP_LOGGER
99

src/viur/shop/payment_providers/unzer_googlepay.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import unzer
44
from unzer.model import PaymentType
5-
65
from viur.core.skeleton import SkeletonInstance
6+
77
from .unzer_abstract import UnzerAbstract
88
from ..globals import SHOP_LOGGER
99

src/viur/shop/payment_providers/unzer_paylater_invoice.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
import typing as t # noqa
33

44
import unzer
5-
6-
from viur import toolkit
75
from viur.core import current, db, errors, exposed
86
from viur.core.skeleton import SkeletonInstance
7+
8+
from viur import toolkit
99
from viur.shop.skeletons import OrderSkel
1010
from viur.shop.types import *
1111
from .unzer_abstract import UnzerAbstract, log_unzer_error

0 commit comments

Comments
 (0)