Skip to content

Commit

Permalink
✨ Changes in auto-recharge limit policy (🗃️, ⚠️) (#4946)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov authored Oct 30, 2023
1 parent da7ba29 commit 809a4d6
Show file tree
Hide file tree
Showing 16 changed files with 173 additions and 139 deletions.
3 changes: 2 additions & 1 deletion .env-devel
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ LOG_FORMAT_LOCAL_DEV_ENABLED=1
WEBSERVER_PAYMENTS={}
PAYMENTS_ACCESS_TOKEN_EXPIRE_MINUTES=30
PAYMENTS_ACCESS_TOKEN_SECRET_KEY=2c0411810565e063309be1457009fb39ce023946f6a354e6935107b57676
PAYMENTS_AUTORECHARGE_DEFAULT_MIN_BALANCE=20.0
PAYMENTS_AUTORECHARGE_DEFAULT_MONTHLY_LIMIT=10000
PAYMENTS_AUTORECHARGE_DEFAULT_TOP_UP_AMOUNT=100.0
PAYMENTS_AUTORECHARGE_MIN_BALANCE_IN_CREDITS=100
PAYMENTS_FAKE_COMPLETION_DELAY_SEC=10
PAYMENTS_FAKE_COMPLETION=0
PAYMENTS_GATEWAY_API_SECRET=replace-with-api-secret
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from decimal import Decimal
from typing import Any, ClassVar, Literal, TypeAlias

from pydantic import Field, HttpUrl, PositiveInt
from pydantic import Field, HttpUrl

from ..basic_types import IDStr, NonNegativeDecimal
from ..users import GroupID
Expand Down Expand Up @@ -171,23 +171,23 @@ class GetWalletAutoRecharge(OutputSchema):
...,
description="Payment method in the wallet used to perform the auto-recharge payments or None if still undefined",
)
min_balance_in_usd: NonNegativeDecimal = Field(
min_balance_in_credits: NonNegativeDecimal = Field(
...,
description="Minimum balance in USD that triggers an auto-recharge",
description="Minimum balance in credits that triggers an auto-recharge [Read only]",
)
top_up_amount_in_usd: NonNegativeDecimal = Field(
...,
description="Amount in USD payed when auto-recharge condition is satisfied",
)
top_up_countdown: PositiveInt | None = Field(
default=None,
description="Maximum number of top-ups left or None to denote unlimited",
monthly_limit_in_usd: NonNegativeDecimal | None = Field(
...,
description="Maximum amount in USD charged within a natural month."
"None indicates no limit.",
)


class ReplaceWalletAutoRecharge(InputSchema):
enabled: bool
payment_method_id: PaymentMethodID
min_balance_in_usd: NonNegativeDecimal
top_up_amount_in_usd: NonNegativeDecimal
top_up_countdown: PositiveInt | None
monthly_limit_in_usd: NonNegativeDecimal | None
11 changes: 9 additions & 2 deletions packages/models-library/src/models_library/invitations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime

from pydantic import BaseModel, Field, PositiveInt
from pydantic import BaseModel, Field, PositiveInt, validator

from .emails import LowerCaseEmailStr

Expand All @@ -10,7 +10,7 @@ class InvitationInputs(BaseModel):

issuer: str = Field(
...,
description="Identifies who issued the invitation. E.g. an email, a service name etc",
description="Identifies who issued the invitation. E.g. an email, a service name etc. NOTE: it will be trimmed if exceeds maximum",
min_length=1,
max_length=30,
)
Expand All @@ -28,6 +28,13 @@ class InvitationInputs(BaseModel):
description="If set, the account's primary wallet will add extra credits corresponding to this ammount in USD",
)

@validator("issuer", pre=True)
@classmethod
def trim_long_issuers_to_max_length(cls, v):
if v and isinstance(v, str):
return v[:29]
return v


class InvitationContent(InvitationInputs):
"""Data in an invitation"""
Expand Down
2 changes: 1 addition & 1 deletion packages/postgres-database/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.10.0
0.11.0
2 changes: 1 addition & 1 deletion packages/postgres-database/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.10.0
current_version = 0.11.0
commit = True
message = packages/postgres-database version: {current_version} → {new_version}
tag = False
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""rm cols in payments_autorecharge
Revision ID: d0d544695487
Revises: 2a4b4167e088
Create Date: 2023-10-27 18:29:46.409910+00:00
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "d0d544695487"
down_revision = "2a4b4167e088"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"payments_autorecharge",
sa.Column("monthly_limit_in_usd", sa.Numeric(scale=2), nullable=True),
)
op.drop_column("payments_autorecharge", "top_up_countdown")
op.drop_column("payments_autorecharge", "min_balance_in_usd")
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"payments_autorecharge",
sa.Column(
"min_balance_in_usd",
sa.NUMERIC(),
server_default=sa.text("0"),
autoincrement=False,
nullable=False,
),
)
op.add_column(
"payments_autorecharge",
sa.Column("top_up_countdown", sa.INTEGER(), autoincrement=False, nullable=True),
)
op.drop_column("payments_autorecharge", "monthly_limit_in_usd")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
Expand Up @@ -60,37 +60,23 @@
# delete the line which will result in wallet default introduced by the api-layer. The only dissadvantage is that
# the user would loose his previous settings.
),
sa.Column(
"min_balance_in_usd",
sa.Numeric(**NUMERIC_KWARGS), # type: ignore
nullable=False,
server_default=sa.text("0"),
doc="[Required] Minimum or equal balance in USD that triggers auto-recharge",
),
sa.Column(
"top_up_amount_in_usd",
sa.Numeric(**NUMERIC_KWARGS), # type: ignore
nullable=False,
doc="[Required] Increase in USD when balance reaches min_balance_in_usd",
doc="[Required] Increase in USD when balance reaches minimum balance threshold",
),
sa.Column(
"top_up_countdown",
sa.Integer(),
"monthly_limit_in_usd",
sa.Numeric(**NUMERIC_KWARGS), # type: ignore
nullable=True,
server_default=None,
doc="[Optional] Number of auto-recharges left."
"If it reaches zero, then auto-recharge stops."
"Used to limit the number of times that the system can auto-recharge."
"If None, then inc has no limit.",
doc="[Optional] Maximum amount in USD charged within a natural month"
"If None, indicates no limit",
),
# time-stamps
column_created_datetime(timezone=True),
column_modified_datetime(timezone=True),
#
sa.CheckConstraint(
"(top_up_countdown >= 0) OR (top_up_countdown IS NULL)",
name="check_top_up_countdown_nonnegative",
),
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ def get_wallet_autorecharge(wallet_id) -> sa.sql.Select:
payments_methods.c.wallet_id,
payments_autorecharge.c.primary_payment_method_id,
payments_autorecharge.c.enabled,
payments_autorecharge.c.min_balance_in_usd,
payments_autorecharge.c.top_up_amount_in_usd,
payments_autorecharge.c.top_up_countdown,
payments_autorecharge.c.monthly_limit_in_usd,
)
.select_from(
payments_methods.join(
Expand All @@ -52,19 +51,17 @@ def upsert_wallet_autorecharge(
wallet_id,
enabled,
primary_payment_method_id,
min_balance_in_usd,
top_up_amount_in_usd,
top_up_countdown,
monthly_limit_in_usd,
):
# using this primary payment-method, create an autorecharge
# NOTE: requires the entire
values = {
"wallet_id": wallet_id,
"enabled": enabled,
"primary_payment_method_id": primary_payment_method_id,
"min_balance_in_usd": min_balance_in_usd,
"top_up_amount_in_usd": top_up_amount_in_usd,
"top_up_countdown": top_up_countdown,
"monthly_limit_in_usd": monthly_limit_in_usd,
}

insert_stmt = pg_insert(payments_autorecharge).values(**values)
Expand All @@ -81,17 +78,3 @@ def update_wallet_autorecharge(wallet_id, **values) -> sa.sql.Update:
.where(payments_autorecharge.c.wallet_id == wallet_id)
.returning(payments_autorecharge.c.id)
)

@staticmethod
def decrease_wallet_autorecharge_countdown(wallet_id) -> sa.sql.Update:
return (
payments_autorecharge.update()
.where(
(payments_autorecharge.c.wallet_id == wallet_id)
& (payments_autorecharge.c.top_up_countdown is not None)
)
.values(
top_up_countdown=payments_autorecharge.c.top_up_countdown - 1,
)
.returning(payments_autorecharge.c.top_up_countdown)
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from aiopg.sa.result import RowProxy
from faker import Faker
from pytest_simcore.helpers.rawdata_fakers import random_payment_method, utcnow
from simcore_postgres_database import errors
from simcore_postgres_database.models.payments_methods import (
InitPromptAckFlowState,
payments_methods,
Expand Down Expand Up @@ -47,19 +46,17 @@ async def _upsert_autorecharge(
wallet_id,
enabled,
primary_payment_method_id,
min_balance_in_usd,
top_up_amount_in_usd,
top_up_countdown,
monthly_limit_in_usd,
) -> RowProxy:
# using this primary payment-method, create an autorecharge
# NOTE: requires the entire
stmt = AutoRechargeStmts.upsert_wallet_autorecharge(
wallet_id=wallet_id,
enabled=enabled,
primary_payment_method_id=primary_payment_method_id,
min_balance_in_usd=min_balance_in_usd,
top_up_amount_in_usd=top_up_amount_in_usd,
top_up_countdown=top_up_countdown,
monthly_limit_in_usd=monthly_limit_in_usd,
)
row = await (await connection.execute(stmt)).first()
assert row
Expand All @@ -71,12 +68,6 @@ async def _update_autorecharge(connection, wallet_id, **settings) -> int | None:
return await connection.scalar(stmt)


async def _decrease_countdown(connection, wallet_id) -> int | None:
stmt = AutoRechargeStmts.decrease_wallet_autorecharge_countdown(wallet_id)
# updates payments countdown
return await connection.scalar(stmt)


PaymentMethodRow: TypeAlias = RowProxy


Expand Down Expand Up @@ -123,41 +114,24 @@ async def test_payments_automation_workflow(
wallet_id,
enabled=True,
primary_payment_method_id=payment_method_id,
min_balance_in_usd=10,
top_up_amount_in_usd=100,
top_up_countdown=5,
monthly_limit_in_usd=None,
)

auto_recharge = await _get_auto_recharge(connection, wallet_id)
assert auto_recharge is not None
assert auto_recharge.primary_payment_method_id == payment_method_id
assert auto_recharge.enabled is True

# countdown
assert await _decrease_countdown(connection, wallet_id) == 4
assert await _decrease_countdown(connection, wallet_id) == 3
assert await _decrease_countdown(connection, wallet_id) == 2
assert await _decrease_countdown(connection, wallet_id) == 1
assert await _decrease_countdown(connection, wallet_id) == 0

with pytest.raises(errors.CheckViolation) as err_info:
await _decrease_countdown(connection, wallet_id)

exc = err_info.value
assert exc.pgerror
assert "check_top_up_countdown_nonnegative" in exc.pgerror

# upsert: deactivate countdown
auto_recharge = await _upsert_autorecharge(
connection,
wallet_id,
enabled=True,
primary_payment_method_id=payment_method_id,
min_balance_in_usd=10,
top_up_amount_in_usd=100,
top_up_countdown=None, # <----
monthly_limit_in_usd=10000, # <----
)
assert auto_recharge.top_up_countdown is None
assert auto_recharge.monthly_limit_in_usd == 10000

await _update_autorecharge(connection, wallet_id, top_up_countdown=None)
assert await _decrease_countdown(connection, wallet_id) is None
await _update_autorecharge(connection, wallet_id, monthly_limit_in_usd=None)
3 changes: 2 additions & 1 deletion services/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,9 @@ services:
TWILIO_COUNTRY_CODES_W_ALPHANUMERIC_SID_SUPPORT: ${TWILIO_COUNTRY_CODES_W_ALPHANUMERIC_SID_SUPPORT}

WEBSERVER_PAYMENTS: ${WEBSERVER_PAYMENTS}
PAYMENTS_AUTORECHARGE_DEFAULT_MIN_BALANCE: ${PAYMENTS_AUTORECHARGE_DEFAULT_MIN_BALANCE}
PAYMENTS_AUTORECHARGE_DEFAULT_MONTHLY_LIMIT: ${PAYMENTS_AUTORECHARGE_DEFAULT_MONTHLY_LIMIT}
PAYMENTS_AUTORECHARGE_DEFAULT_TOP_UP_AMOUNT: ${PAYMENTS_AUTORECHARGE_DEFAULT_TOP_UP_AMOUNT}
PAYMENTS_AUTORECHARGE_MIN_BALANCE_IN_CREDITS: ${PAYMENTS_AUTORECHARGE_MIN_BALANCE_IN_CREDITS}
PAYMENTS_FAKE_COMPLETION_DELAY_SEC: ${PAYMENTS_FAKE_COMPLETION_DELAY_SEC}
PAYMENTS_FAKE_COMPLETION: ${PAYMENTS_FAKE_COMPLETION}
PAYMENTS_FAKE_GATEWAY_URL: ${PAYMENTS_GATEWAY_URL}
Expand Down
6 changes: 3 additions & 3 deletions services/invitations/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
"maxLength": 30,
"minLength": 1,
"type": "string",
"description": "Identifies who issued the invitation. E.g. an email, a service name etc"
"description": "Identifies who issued the invitation. E.g. an email, a service name etc. NOTE: it will be trimmed if exceeds maximum"
},
"guest": {
"title": "Guest",
Expand Down Expand Up @@ -225,7 +225,7 @@
"maxLength": 30,
"minLength": 1,
"type": "string",
"description": "Identifies who issued the invitation. E.g. an email, a service name etc"
"description": "Identifies who issued the invitation. E.g. an email, a service name etc. NOTE: it will be trimmed if exceeds maximum"
},
"guest": {
"title": "Guest",
Expand Down Expand Up @@ -284,7 +284,7 @@
"maxLength": 30,
"minLength": 1,
"type": "string",
"description": "Identifies who issued the invitation. E.g. an email, a service name etc"
"description": "Identifies who issued the invitation. E.g. an email, a service name etc. NOTE: it will be trimmed if exceeds maximum"
},
"guest": {
"title": "Guest",
Expand Down
Loading

0 comments on commit 809a4d6

Please sign in to comment.