Skip to content

Commit

Permalink
✨ New pay with payment-method implementation (#5017)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov authored Nov 16, 2023
1 parent 633ec55 commit 5477e3d
Show file tree
Hide file tree
Showing 33 changed files with 489 additions and 255 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -178,5 +178,6 @@ Untitled*

# service settings.schemas.json
services/**/settings-schema.json
services/payments/scripts/openapi.json

tests/public-api/osparc_python_wheels/*
2 changes: 2 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"recommendations": [
"42Crunch.vscode-openapi",
"charliermarsh.ruff",
"eamodio.gitlens",
"exiasr.hadolint",
"ms-azuretools.vscode-docker",
"ms-python.black-formatter",
"ms-python.pylint",
"ms-python.python",
"ms-vscode.makefile-tools",
"njpwerner.autodocstring",
"samuelcolvin.jinjahtml",
"timonwong.shellcheck",
Expand Down
4 changes: 2 additions & 2 deletions api/specs/web-server/_wallets.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,10 @@ async def delete_payment_method(
@router.post(
"/wallets/{wallet_id}/payments-methods/{payment_method_id}:pay",
response_model=Envelope[WalletPaymentInitiated],
response_description="Payment initialized",
response_description="Pay with payment-method",
status_code=status.HTTP_202_ACCEPTED,
)
async def init_payment_with_payment_method(
async def pay_with_payment_method(
wallet_id: WalletID, payment_method_id: PaymentMethodID, _body: CreateWalletPayment
):
...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from pydantic import Field, HttpUrl

from ..basic_types import IDStr, NonNegativeDecimal
from ..basic_types import AmountDecimal, IDStr, NonNegativeDecimal
from ..users import GroupID
from ..utils.pydantic_tools_extension import FieldNotRequired
from ..wallets import WalletID, WalletStatus
Expand Down Expand Up @@ -55,7 +55,7 @@ class PutWalletBodyParams(OutputSchema):


class CreateWalletPayment(InputSchema):
price_dollars: Decimal
price_dollars: AmountDecimal
comment: str = FieldNotRequired(max_length=100)


Expand Down Expand Up @@ -124,14 +124,11 @@ class Config(OutputSchema.Config):
class PaymentMethodGet(OutputSchema):
idr: PaymentMethodID
wallet_id: WalletID
card_holder_name: str
card_number_masked: str
card_type: str
expiration_month: int
expiration_year: int
street_address: str
zipcode: str
country: str
card_holder_name: str | None = None
card_number_masked: str | None = None
card_type: str | None = None
expiration_month: int | None = None
expiration_year: int | None = None
created: datetime
auto_recharge: bool = Field(
default=False,
Expand All @@ -149,12 +146,15 @@ class Config(OutputSchema.Config):
"cardType": "Visa",
"expirationMonth": 10,
"expirationYear": 2025,
"streetAddress": "123 Main St",
"zipcode": "12345",
"country": "United States",
"created": "2023-09-13T15:30:00Z",
"autoRecharge": "False",
},
{
"idr": "pm_1234567890",
"walletId": 3,
"created": "2024-09-13T15:30:00Z",
"autoRecharge": "False",
},
],
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,16 +251,14 @@ def random_api_key(product_name: str, user_id: int, **overrides) -> dict[str, An
def random_payment_method_data(**overrides) -> dict[str, Any]:
# Produces data for GetPaymentMethod
data = {
"idr": FAKE.uuid4(),
"id": FAKE.uuid4(),
"card_holder_name": FAKE.name(),
"card_number_masked": f"**** **** **** {FAKE.credit_card_number()[:4]}",
"card_type": FAKE.credit_card_provider(),
"expiration_month": FAKE.random_int(min=1, max=12),
"expiration_year": FAKE.future_date().year,
"street_address": FAKE.street_address(),
"zipcode": FAKE.zipcode(),
"country": FAKE.country(),
"created": utcnow(),
}
assert set(overrides.keys()).issubset(data.keys())
data.update(**overrides)
return data
4 changes: 2 additions & 2 deletions services/payments/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ external ?= .env-secret

test-dev-unit-external: ## runs test-dev against external service defined in $(external) envfile
# Running tests using external environ '$(external)'
$(MAKE) test-dev-unit pytest-parameters="--external-envfile=$(external)"
$(MAKE) test-dev-unit pytest-parameters="--external-envfile=$(external) -m can_run_against_external"

test-ci-unit-external: ## runs test-ci against external service defined in $(external) envfile
# Running tests using external environ '$(external)'
$(MAKE) test-ci-unit pytest-parameters="--external-envfile=$(external)"
$(MAKE) test-ci-unit pytest-parameters="--external-envfile=$(external) -m can_run_against_external"
2 changes: 1 addition & 1 deletion services/payments/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.3.1
1.4.0
14 changes: 11 additions & 3 deletions services/payments/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"info": {
"title": "simcore-service-payments web API",
"description": " Service that manages creation and validation of registration payments",
"version": "1.3.1"
"version": "1.4.0"
},
"paths": {
"/": {
Expand Down Expand Up @@ -220,6 +220,13 @@
"type": "string",
"title": "Message"
},
"provider_payment_id": {
"type": "string",
"maxLength": 50,
"minLength": 1,
"title": "Provider Payment Id",
"description": "Payment ID from the provider (e.g. stripe payment ID)"
},
"invoice_url": {
"type": "string",
"maxLength": 2083,
Expand All @@ -235,7 +242,7 @@
}
],
"title": "Saved",
"description": "If the user decided to save the payment methodafter payment it returns the payment-method acknoledgement response.Otherwise it defaults to None."
"description": "Gets the payment-method if user opted to save it during payment.If used did not opt to save of payment-method was already saved, then it defaults to None"
}
},
"type": "object",
Expand All @@ -245,10 +252,11 @@
"title": "AckPayment",
"example": {
"success": true,
"provider_payment_id": "pi_123ABC",
"invoice_url": "https://invoices.com/id=12345",
"saved": {
"success": true,
"payment_method_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
"payment_method_id": "3FA85F64-5717-4562-B3FC-2C963F66AFA6"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion services/payments/scripts/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ run-devel: ## runs fake_payment_gateway server
uvicorn fake_payment_gateway:the_app --reload


openapi.json:
openapi.json: ## creates OAS
@set -o allexport; source .env-secret; set +o allexport; \
python fake_payment_gateway.py openapi > $@
13 changes: 8 additions & 5 deletions services/payments/scripts/fake_payment_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@
PaymentMethodInitiated,
PaymentMethodsBatch,
)
from simcore_service_payments.models.schemas.acknowledgements import AckPayment
from simcore_service_payments.models.schemas.acknowledgements import (
AckPayment,
AckPaymentWithPaymentMethod,
)
from simcore_service_payments.models.schemas.auth import Token

logging.basicConfig(level=logging.INFO)
Expand Down Expand Up @@ -291,7 +294,7 @@ def batch_get_payment_methods(

@router.get(
"/{id}",
response_class=GetPaymentMethod,
response_model=GetPaymentMethod,
responses=ERROR_RESPONSES,
)
def get_payment_method(
Expand All @@ -315,10 +318,10 @@ def delete_payment_method(

@router.post(
"/{id}:pay",
response_model=PaymentInitiated,
response_model=AckPaymentWithPaymentMethod,
responses=ERROR_RESPONSES,
)
def init_payment_with_payment_method(
def pay_with_payment_method(
id: PaymentMethodID,
payment: InitPayment,
auth: Annotated[int, Depends(auth_session)],
Expand All @@ -344,7 +347,7 @@ async def _app_lifespan(app: FastAPI):
def create_app():
app = FastAPI(
title="fake-payment-gateway",
version="0.2.1",
version="0.3.0",
lifespan=_app_lifespan,
debug=True,
)
Expand Down
4 changes: 3 additions & 1 deletion services/payments/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
[bumpversion]
current_version = 1.3.1
current_version = 1.4.0
commit = True
message = services/payments version: {current_version} → {new_version}
tag = False
commit_args = --no-verify

[bumpversion:file:VERSION]
[bumpversion:file:openapi.json]

[tool:pytest]
asyncio_mode = auto
markers =
testit: "marks test to run during development"
acceptance_test: "marks tests as 'acceptance tests' i.e. does the system do what the user expects? Typically those are workflows."
can_run_against_external: "marks tests that *can* be run against an external configuration passed by --external-envfile"
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ...db.base import BaseRepository
from ...models.auth import SessionData
from ...services.auth import get_session_data
from ...services.postgres import get_engine
from ...services.resource_usage_tracker import ResourceUsageTrackerApi

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -43,7 +44,7 @@ def get_rut_api(request: Request) -> ResourceUsageTrackerApi:


def get_db_engine(request: Request) -> AsyncEngine:
engine: AsyncEngine = request.app.state.engine
engine: AsyncEngine = get_engine(request.app)
assert engine # nosec
return engine

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
PaymentMethodGet,
PaymentMethodID,
PaymentMethodInitiated,
WalletPaymentInitiated,
)
from models_library.basic_types import IDStr
from models_library.users import UserID
Expand Down Expand Up @@ -113,7 +112,7 @@ async def delete_payment_method(


@router.expose()
async def init_payment_with_payment_method( # noqa: PLR0913 # pylint: disable=too-many-arguments
async def pay_with_payment_method( # noqa: PLR0913 # pylint: disable=too-many-arguments
app: FastAPI,
*,
payment_method_id: PaymentMethodID,
Expand All @@ -126,8 +125,8 @@ async def init_payment_with_payment_method( # noqa: PLR0913 # pylint: disable=t
user_name: str,
user_email: EmailStr,
comment: str | None = None,
) -> WalletPaymentInitiated:
return await payments.init_payment_with_payment_method(
):
return await payments.pay_with_payment_method(
gateway=PaymentsGatewayApi.get_from_app_state(app),
repo_transactions=PaymentsTransactionsRepo(db_engine=app.state.engine),
repo_methods=PaymentsMethodsRepo(db_engine=app.state.engine),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ async def insert_init_payment_transaction(
initiated_at: datetime.datetime,
) -> PaymentID:
"""Annotates init-payment transaction
Raises:
PaymentAlreadyExistsError
"""

try:
async with self.db_engine.begin() as conn:
await conn.execute(
Expand Down
28 changes: 27 additions & 1 deletion services/payments/src/simcore_service_payments/models/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
from decimal import Decimal
from typing import Any, ClassVar

from models_library.api_schemas_webserver.wallets import PaymentID, PaymentMethodID
from models_library.api_schemas_webserver.wallets import (
PaymentID,
PaymentMethodID,
PaymentTransaction,
)
from models_library.emails import LowerCaseEmailStr
from models_library.products import ProductName
from models_library.users import UserID
Expand Down Expand Up @@ -59,6 +63,28 @@ class Config:
]
}

def to_api_model(self) -> PaymentTransaction:
data: dict[str, Any] = {
"payment_id": self.payment_id,
"price_dollars": self.price_dollars,
"osparc_credits": self.osparc_credits,
"wallet_id": self.wallet_id,
"created_at": self.initiated_at,
"state": self.state,
"completed_at": self.completed_at,
}

if self.comment:
data["comment"] = self.comment

if self.state_message:
data["state_message"] = self.state_message

if self.invoice_url:
data["invoice_url"] = self.invoice_url

return PaymentTransaction.parse_obj(data)


_EXAMPLE_AFTER_INIT_PAYMENT_METHOD = {
"payment_method_id": "12345",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,12 @@ class PaymentMethodInitiated(BaseModel):


class GetPaymentMethod(BaseModel):
idr: PaymentMethodID
card_holder_name: str
card_number_masked: str
card_type: str
expiration_month: int
expiration_year: int
street_address: str
zipcode: str
country: str
id: PaymentMethodID
card_holder_name: str | None = None
card_number_masked: str | None = None
card_type: str | None = None
expiration_month: int | None = None
expiration_year: int | None = None
created: datetime


Expand Down
Loading

0 comments on commit 5477e3d

Please sign in to comment.