aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstuppie2026-09-23 13:14:01 -0600
committerstuppie2026-09-23 13:14:01 -0600
commit56f452abd019e83ecdd7c57ea2da4a15d2b921fe (patch)
tree0f90fdf195612284ed815375735aa46903b99618
parente4ba113252cdf3e7e039e2a57ea7ec9372360190 (diff)
downloadgeneralresearch-3.5.7.tar.gz
generalresearch-3.5.7.zip
add a single get_cashout_method method for use by panels in lookup of cashout info, and clean up the cashout method currency post-processingv3.5.7
-rw-r--r--generalresearch/managers/thl/cashout_method.py118
-rw-r--r--generalresearch/models/legacy/api_status.py4
-rw-r--r--generalresearch/models/thl/wallet/cashout_method.py8
-rw-r--r--pyproject.toml2
4 files changed, 96 insertions, 36 deletions
diff --git a/generalresearch/managers/thl/cashout_method.py b/generalresearch/managers/thl/cashout_method.py
index 8a4236b..7f56423 100644
--- a/generalresearch/managers/thl/cashout_method.py
+++ b/generalresearch/managers/thl/cashout_method.py
@@ -20,6 +20,7 @@ from generalresearch.models.thl.wallet.definitions import (
if TYPE_CHECKING:
from generalresearch.managers.thl.wallet.tango import TangoManager
+ from generalresearch.models.thl.product import Product
from generalresearch.models.thl.user import User
from generalresearch.models.thl.wallet.cashout_method import (
CashMailCashoutMethodData,
@@ -246,6 +247,93 @@ class CashoutMethodManager(PostgresManager):
)
return [self.format_from_db(x, user=user) for x in res]
+ def get_cashout_method(
+ self,
+ cashout_method_id: str,
+ product_id: str,
+ usd_exchange_rate: dict[Currency, float],
+ ) -> CashoutMethod:
+ from generalresearch.managers.thl.product import ProductManager
+
+ product = ProductManager(pg_config=self.pg_config).get_by_uuid(
+ product_uuid=product_id
+ )
+ supported_payout_types = copy(product.user_wallet_config.supported_payout_types)
+ if product.user_wallet_config.amt:
+ supported_payout_types.add(PayoutType.AMT)
+ res = self.pg_config.execute_sql_query(
+ query="""
+ SELECT ac.id::uuid, ac.provider, ac.ext_id,
+ ac.data::jsonb as _data_, ac.user_id, u.product_user_id
+ FROM accounting_cashoutmethod AS ac
+ LEFT JOIN thl_user AS u
+ ON ac.user_id = u.id AND u.product_id = %(product_id)s
+ WHERE ac.id = %(cashout_method_id)s
+ AND ac.is_live
+ AND (ac.user_id IS NULL OR u.id IS NOT NULL)
+ AND ac.provider = ANY(%(supported_payout_types)s)
+ LIMIT 1
+ """,
+ params={
+ "cashout_method_id": cashout_method_id,
+ "product_id": product_id,
+ "supported_payout_types": [
+ payout_type.value for payout_type in supported_payout_types
+ ],
+ },
+ )
+ assert res, (
+ f"No cashout method found with id {cashout_method_id} "
+ f"for product {product_id}"
+ )
+ row = res[0]
+ user = None
+ if row["user_id"] is not None:
+ user = UserRef(
+ user_id=row["user_id"],
+ product_id=product_id,
+ product_user_id=row.pop("product_user_id"),
+ )
+ cashout_method = self.format_from_db(row, user=user)
+ cashout_method = self.apply_currency_adjustments(
+ cashout_method=cashout_method,
+ product=product,
+ usd_exchange_rate=usd_exchange_rate,
+ )
+ assert cashout_method is not None, (
+ f"Cashout method {cashout_method_id} is unavailable for product {product_id}"
+ )
+ return cashout_method
+
+ @staticmethod
+ def apply_currency_adjustments(
+ cashout_method: CashoutMethod,
+ product: Product,
+ usd_exchange_rate: dict[Currency, float],
+ ) -> CashoutMethod | None:
+ min_value_usd = product.user_wallet_config.min_cashout or 0
+ product_min_value = USDCent(round(min_value_usd * 100))
+
+ if cashout_method.original_currency in {None, Currency.USD}:
+ exchange_rate = 1.0
+ else:
+ exchange_rate = usd_exchange_rate[cashout_method.original_currency]
+
+ method_min_value_usd = USDCent(round(cashout_method.min_value * exchange_rate))
+ min_value = max(method_min_value_usd, product_min_value)
+ max_value = USDCent(round(cashout_method.max_value * exchange_rate))
+ if min_value > max_value:
+ return None
+
+ cashout_method.usd_exchange_rate = exchange_rate
+ cashout_method.min_value_usd = min_value
+ cashout_method.max_value_usd = max_value
+ cashout_method.min_value = max(
+ cashout_method.min_value,
+ round(int(product_min_value) / exchange_rate),
+ )
+ return cashout_method
+
def get_cashout_methods(self, user: User) -> list[CashoutMethod]:
"""
The provider column is PayoutType. Some are only user-scoped,
@@ -324,8 +412,6 @@ class CashoutMethodManager(PostgresManager):
# BP can set their own min in USD or equivalent
user.prefetch_product(pg_config=self.pg_config)
product = user.product
- min_value_usd = product.user_wallet_config.min_cashout or 0
- min_value = USDCent(round(min_value_usd * 100))
cms = self.get_cashout_methods(user=user)
@@ -339,34 +425,10 @@ class CashoutMethodManager(PostgresManager):
or cm.type != PayoutType.TANGO
]
- for x in cms:
- # assets in non-USD need to be converted to USD here
- if x.original_currency is not None:
- if x.original_currency == Currency.USD:
- x.usd_exchange_rate = 1.0
- else:
- x.usd_exchange_rate = usd_exchange_rate[x.original_currency]
- # If the user has foreign cards available, we need to show their min_value in USD
- x.min_value_usd = max(
- USDCent(round(x.min_value * x.usd_exchange_rate)),
- min_value,
- )
- x.max_value_usd = USDCent(round(x.max_value * x.usd_exchange_rate))
- # Keep min_value in the method's original currency and use the
- # same conversion and rounding as TangoManager.make_request.
- x.min_value = max(
- x.min_value,
- round(int(min_value) / x.usd_exchange_rate),
- )
-
- # A product minimum above the method maximum makes the method unusable.
cms = [
- x
- for x in cms
- if x.min_value_usd is None
- or x.max_value_usd is None
- or x.min_value_usd <= x.max_value_usd
+ self.apply_currency_adjustments(x, product, usd_exchange_rate) for x in cms
]
+ cms = [x for x in cms if x is not None]
return {x.id: x for x in cms}
diff --git a/generalresearch/models/legacy/api_status.py b/generalresearch/models/legacy/api_status.py
index 0422191..844dff5 100644
--- a/generalresearch/models/legacy/api_status.py
+++ b/generalresearch/models/legacy/api_status.py
@@ -12,7 +12,9 @@ from pydantic import BaseModel, Field
class StatusResponse(BaseModel):
status: Literal["success", "error"] = Field(
- description="The status of the API response.", examples=["success"]
+ description="The status of the API response.",
+ examples=["success"],
+ default="success",
)
msg: str | None = Field(
description="An optional message, if the status is error.",
diff --git a/generalresearch/models/thl/wallet/cashout_method.py b/generalresearch/models/thl/wallet/cashout_method.py
index 37a5a74..b1bd1eb 100644
--- a/generalresearch/models/thl/wallet/cashout_method.py
+++ b/generalresearch/models/thl/wallet/cashout_method.py
@@ -143,12 +143,8 @@ class CashoutMethodBase(BaseModel):
self.min_value_usd is None or self.max_value_usd is None
):
raise ValueError("USD limits are required for a foreign cashout method")
- min_value = (
- self.min_value if self.min_value_usd is None else self.min_value_usd
- )
- max_value = (
- self.max_value if self.max_value_usd is None else self.max_value_usd
- )
+ min_value = self.min_value if self.min_value_usd is None else self.min_value_usd
+ max_value = self.max_value if self.max_value_usd is None else self.max_value_usd
if not min_value <= amount <= max_value:
raise ValueError(
f"Invalid amount requested: ${amount / 100:.2f}. Must be between"
diff --git a/pyproject.toml b/pyproject.toml
index d9f59a9..3a3c544 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "generalresearch"
-version = "3.5.6"
+version = "3.5.7"
description = "Python Utilities for General Research"
readme = "README.md"
requires-python = ">=3.14"