Skip to content

Commit 947d928

Browse files
authored
payments service enables notifications via sio (#5109)
1 parent a399895 commit 947d928

File tree

20 files changed

+362
-144
lines changed

20 files changed

+362
-144
lines changed

packages/models-library/src/models_library/invitations.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from datetime import datetime, timezone
2+
from typing import Final
23

34
from models_library.products import ProductName
45
from pydantic import BaseModel, EmailStr, Field, PositiveInt, validator
56

7+
_MAX_LEN: Final = 40
8+
69

710
class InvitationInputs(BaseModel):
811
"""Input data necessary to create an invitation"""
@@ -11,7 +14,7 @@ class InvitationInputs(BaseModel):
1114
...,
1215
description="Identifies who issued the invitation. E.g. an email, a service name etc. NOTE: it will be trimmed if exceeds maximum",
1316
min_length=1,
14-
max_length=30,
17+
max_length=_MAX_LEN,
1518
)
1619
guest: EmailStr = Field(
1720
...,
@@ -35,7 +38,7 @@ class InvitationInputs(BaseModel):
3538
@classmethod
3639
def trim_long_issuers_to_max_length(cls, v):
3740
if v and isinstance(v, str):
38-
return v[:29]
41+
return v[: _MAX_LEN - 1]
3942
return v
4043

4144

packages/pytest-simcore/src/pytest_simcore/helpers/rawdata_fakers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from faker import Faker
2424
from simcore_postgres_database.models.api_keys import api_keys
2525
from simcore_postgres_database.models.comp_pipeline import StateType
26+
from simcore_postgres_database.models.groups import groups
2627
from simcore_postgres_database.models.payments_methods import InitPromptAckFlowState
2728
from simcore_postgres_database.models.payments_transactions import (
2829
PaymentTransactionState,
@@ -101,11 +102,15 @@ def random_project(**overrides) -> dict[str, Any]:
101102

102103

103104
def random_group(**overrides) -> dict[str, Any]:
105+
104106
data = {
105107
"name": FAKE.company(),
106108
"description": FAKE.text(),
107109
"type": GroupType.STANDARD.name,
108110
}
111+
112+
assert set(data.keys()).issubset({c.name for c in groups.columns}) # nosec
113+
109114
data.update(overrides)
110115
return data
111116

packages/service-library/src/servicelib/fastapi/http_client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ def set_to_app_state(self, app: FastAPI):
8282

8383
@classmethod
8484
def pop_from_app_state(cls, app: FastAPI):
85-
old = getattr(app.state, cls.app_state_name, None)
85+
"""
86+
Raises:
87+
AttributeError: if instance is not in app.state
88+
"""
89+
old = getattr(app.state, cls.app_state_name)
8690
delattr(app.state, cls.app_state_name)
8791
return old
8892

services/payments/src/simcore_service_payments/api/rest/_acknowledgements.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616
PaymentMethodID,
1717
)
1818
from ...services import payments, payments_methods
19+
from ...services.notifier import Notifier
1920
from ...services.resource_usage_tracker import ResourceUsageTrackerApi
20-
from ._dependencies import get_current_session, get_repository, get_rut_api
21+
from ._dependencies import (
22+
create_repository,
23+
get_current_session,
24+
get_from_app_state,
25+
get_rut_api,
26+
)
2127

2228
_logger = logging.getLogger(__name__)
2329

@@ -31,12 +37,13 @@ async def acknowledge_payment(
3137
ack: AckPayment,
3238
_session: Annotated[SessionData, Depends(get_current_session)],
3339
repo_pay: Annotated[
34-
PaymentsTransactionsRepo, Depends(get_repository(PaymentsTransactionsRepo))
40+
PaymentsTransactionsRepo, Depends(create_repository(PaymentsTransactionsRepo))
3541
],
3642
repo_methods: Annotated[
37-
PaymentsMethodsRepo, Depends(get_repository(PaymentsMethodsRepo))
43+
PaymentsMethodsRepo, Depends(create_repository(PaymentsMethodsRepo))
3844
],
3945
rut_api: Annotated[ResourceUsageTrackerApi, Depends(get_rut_api)],
46+
notifier: Annotated[Notifier, Depends(get_from_app_state(Notifier))],
4047
background_tasks: BackgroundTasks,
4148
):
4249
"""completes (ie. ack) request initated by `/init` on the payments-gateway API"""
@@ -60,26 +67,33 @@ async def acknowledge_payment(
6067

6168
assert f"{payment_id}" == f"{transaction.payment_id}" # nosec
6269
background_tasks.add_task(
63-
payments.on_payment_completed, transaction, rut_api, notify_enabled=True
70+
payments.on_payment_completed, transaction, rut_api, notifier=notifier
6471
)
6572

6673
if ack.saved:
67-
created = await payments_methods.insert_payment_method(
74+
inserted = await payments_methods.insert_payment_method(
6875
repo=repo_methods,
6976
payment_method_id=ack.saved.payment_method_id,
7077
user_id=transaction.user_id,
7178
wallet_id=transaction.wallet_id,
7279
ack=ack.saved,
7380
)
74-
background_tasks.add_task(payments_methods.on_payment_method_completed, created)
81+
background_tasks.add_task(
82+
payments_methods.on_payment_method_completed,
83+
payment_method=inserted,
84+
notifier=notifier,
85+
)
7586

7687

7788
@router.post("/payments-methods/{payment_method_id}:ack")
7889
async def acknowledge_payment_method(
7990
payment_method_id: PaymentMethodID,
8091
ack: AckPaymentMethod,
8192
_session: Annotated[SessionData, Depends(get_current_session)],
82-
repo: Annotated[PaymentsMethodsRepo, Depends(get_repository(PaymentsMethodsRepo))],
93+
repo: Annotated[
94+
PaymentsMethodsRepo, Depends(create_repository(PaymentsMethodsRepo))
95+
],
96+
notifier: Annotated[Notifier, Depends(get_from_app_state(Notifier))],
8397
background_tasks: BackgroundTasks,
8498
):
8599
"""completes (ie. ack) request initated by `/payments-methods:init` on the payments-gateway API"""
@@ -101,4 +115,8 @@ async def acknowledge_payment_method(
101115
) from err
102116

103117
assert f"{payment_method_id}" == f"{acked.payment_method_id}" # nosec
104-
background_tasks.add_task(payments_methods.on_payment_method_completed, acked)
118+
background_tasks.add_task(
119+
payments_methods.on_payment_method_completed,
120+
payment_method=acked,
121+
notifier=notifier,
122+
)

services/payments/src/simcore_service_payments/api/rest/_dependencies.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
from collections.abc import AsyncGenerator, Callable
33
from typing import Annotated, cast
44

5-
from fastapi import Depends, Request
5+
from fastapi import Depends, FastAPI, Request
66
from fastapi.security import OAuth2PasswordBearer
77
from servicelib.fastapi.dependencies import get_app, get_reverse_url_mapper
8+
from servicelib.fastapi.http_client import AppStateMixin
89
from sqlalchemy.ext.asyncio import AsyncEngine
910

1011
from ..._meta import API_VTAG
@@ -43,19 +44,30 @@ def get_rut_api(request: Request) -> ResourceUsageTrackerApi:
4344
)
4445

4546

47+
def get_from_app_state(app_state_mixin_subclass: type[AppStateMixin]) -> Callable:
48+
"""Generic getter of app.state objects"""
49+
50+
def _(app: Annotated[FastAPI, Depends(get_app)]):
51+
return app_state_mixin_subclass.get_from_app_state(app)
52+
53+
return _
54+
55+
4656
def get_db_engine(request: Request) -> AsyncEngine:
4757
engine: AsyncEngine = get_engine(request.app)
4858
assert engine # nosec
4959
return engine
5060

5161

52-
def get_repository(repo_type: type[BaseRepository]) -> Callable:
53-
async def _get_repo(
62+
def create_repository(repo_cls: type[BaseRepository]) -> Callable:
63+
"""Generic object factory of BaseRepository instances"""
64+
65+
async def _(
5466
engine: Annotated[AsyncEngine, Depends(get_db_engine)],
5567
) -> AsyncGenerator[BaseRepository, None]:
56-
yield repo_type(db_engine=engine)
68+
yield repo_cls(db_engine=engine)
5769

58-
return _get_repo
70+
return _
5971

6072

6173
# Implements `password` flow defined in OAuth2

services/payments/src/simcore_service_payments/core/application.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from fastapi import FastAPI
22
from servicelib.fastapi.openapi import override_fastapi_openapi_method
3+
from simcore_service_payments.services.notifier import setup_notifier
34
from simcore_service_payments.services.socketio import setup_socketio
45

56
from .._meta import (
@@ -56,6 +57,7 @@ def create_app(settings: ApplicationSettings | None = None) -> FastAPI:
5657
# Listening to Rabbitmq
5758
setup_auto_recharge_listener(app)
5859
setup_socketio(app)
60+
setup_notifier(app)
5961

6062
# ERROR HANDLERS
6163
# ... add here ...
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import sqlalchemy as sa
2+
from models_library.users import GroupID, UserID
3+
from simcore_postgres_database.models.users import users
4+
5+
from .base import BaseRepository
6+
7+
8+
class PaymentsUsersRepo(BaseRepository):
9+
# NOTE:
10+
# Currently linked to `users` but expected to be linked to `payments_users`
11+
# when databases are separated. The latter will be a subset copy of the former.
12+
#
13+
async def get_primary_group_id(self, user_id: UserID) -> GroupID:
14+
async with self.db_engine.begin() as conn:
15+
result = await conn.execute(
16+
sa.select(users.c.primary_gid).where(users.c.id == user_id)
17+
)
18+
row = result.first()
19+
if row is None:
20+
msg = f"{user_id=} not found"
21+
raise ValueError(msg)
22+
return GroupID(row.primary_gid)

services/payments/src/simcore_service_payments/models/db.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22
from decimal import Decimal
33
from typing import Any, ClassVar
44

5-
from models_library.api_schemas_webserver.wallets import (
6-
PaymentID,
7-
PaymentMethodID,
8-
PaymentTransaction,
9-
)
5+
from models_library.api_schemas_webserver.wallets import PaymentID, PaymentMethodID
106
from models_library.emails import LowerCaseEmailStr
117
from models_library.products import ProductName
128
from models_library.users import UserID
@@ -63,28 +59,6 @@ class Config:
6359
]
6460
}
6561

66-
def to_api_model(self) -> PaymentTransaction:
67-
data: dict[str, Any] = {
68-
"payment_id": self.payment_id,
69-
"price_dollars": self.price_dollars,
70-
"osparc_credits": self.osparc_credits,
71-
"wallet_id": self.wallet_id,
72-
"created_at": self.initiated_at,
73-
"state": self.state,
74-
"completed_at": self.completed_at,
75-
}
76-
77-
if self.comment:
78-
data["comment"] = self.comment
79-
80-
if self.state_message:
81-
data["state_message"] = self.state_message
82-
83-
if self.invoice_url:
84-
data["invoice_url"] = self.invoice_url
85-
86-
return PaymentTransaction.parse_obj(data)
87-
8862

8963
_EXAMPLE_AFTER_INIT_PAYMENT_METHOD = {
9064
"payment_method_id": "12345",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from typing import Any
2+
3+
from models_library.api_schemas_webserver.wallets import (
4+
PaymentMethodTransaction,
5+
PaymentTransaction,
6+
)
7+
8+
from .db import PaymentsMethodsDB, PaymentsTransactionsDB
9+
10+
11+
def to_payments_api_model(transaction: PaymentsTransactionsDB) -> PaymentTransaction:
12+
data: dict[str, Any] = {
13+
"payment_id": transaction.payment_id,
14+
"price_dollars": transaction.price_dollars,
15+
"osparc_credits": transaction.osparc_credits,
16+
"wallet_id": transaction.wallet_id,
17+
"created_at": transaction.initiated_at,
18+
"state": transaction.state,
19+
"completed_at": transaction.completed_at,
20+
}
21+
22+
if transaction.comment:
23+
data["comment"] = transaction.comment
24+
25+
if transaction.state_message:
26+
data["state_message"] = transaction.state_message
27+
28+
if transaction.invoice_url:
29+
data["invoice_url"] = transaction.invoice_url
30+
31+
return PaymentTransaction(**data)
32+
33+
34+
def to_payment_method_api_model(
35+
payment_method: PaymentsMethodsDB,
36+
) -> PaymentMethodTransaction:
37+
return PaymentMethodTransaction(
38+
wallet_id=payment_method.wallet_id,
39+
payment_method_id=payment_method.payment_method_id,
40+
state=payment_method.state.value,
41+
)

0 commit comments

Comments
 (0)