aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstuppie2026-09-18 13:40:54 -0600
committerstuppie2026-09-18 13:40:54 -0600
commit438ea05fdb98c847d534ae190179b4766fa96f26 (patch)
tree45dbe0d228ac2e3b715801c0b4b28e3d244b54c0
parent1a98418317c115b92739639cf0b837bd974fc5d6 (diff)
downloadgeneralresearch-438ea05fdb98c847d534ae190179b4766fa96f26.tar.gz
generalresearch-438ea05fdb98c847d534ae190179b4766fa96f26.zip
test user payout paypal and tango, and fix bugs
-rw-r--r--generalresearch/managers/thl/cashout_method.py2
-rw-r--r--generalresearch/managers/thl/wallet/tango.py6
-rw-r--r--generalresearch/managers/thl/wallet/user_payout.py23
-rw-r--r--generalresearch/models/thl/wallet/cashout_method.py4
-rw-r--r--test_utils/managers/conftest.py25
-rw-r--r--test_utils/managers/thl/conftest.py11
-rw-r--r--tests/managers/thl/test_ledger/test_thl_pem.py32
-rw-r--r--tests/managers/thl/test_payout.py2
-rw-r--r--tests/managers/thl/test_wallet/__init__.py0
-rw-r--r--tests/managers/thl/test_wallet/test_user_payout.py435
10 files changed, 454 insertions, 86 deletions
diff --git a/generalresearch/managers/thl/cashout_method.py b/generalresearch/managers/thl/cashout_method.py
index 46bde38..90a6876 100644
--- a/generalresearch/managers/thl/cashout_method.py
+++ b/generalresearch/managers/thl/cashout_method.py
@@ -376,7 +376,7 @@ class CashoutMethodManager(PostgresManager):
if user and x["type"] in {PayoutType.PAYPAL, PayoutType.CASH_IN_MAIL}:
user = user if isinstance(user, UserRef) else user.to_user_ref()
x["user"] = user
- x["original_currency"] = x.get("currency") or Currency.USD
+ x["original_currency"] = x.get("original_currency") or Currency.USD
x["currency"] = Currency.USD
return CashoutMethod.model_validate(x)
diff --git a/generalresearch/managers/thl/wallet/tango.py b/generalresearch/managers/thl/wallet/tango.py
index ca993ea..361a885 100644
--- a/generalresearch/managers/thl/wallet/tango.py
+++ b/generalresearch/managers/thl/wallet/tango.py
@@ -1,6 +1,7 @@
from __future__ import annotations
from datetime import timedelta
+from decimal import Decimal
from threading import Lock
from typing import TYPE_CHECKING, Any
@@ -69,13 +70,14 @@ class TangoManager:
utid = cashout_method.data.utid
currency = cashout_method.original_currency
if currency and currency != Currency.USD:
- amount = round(float(amount) / self.get_exchange_rates()[currency], 2)
+ amount = round(float(amount) / self.get_exchange_rates()[currency])
+ amount_in_currency = Decimal(float(amount) / 100).quantize(Decimal('0.01'))
return TangoCashoutMethodRequestData.model_validate(
{
"accountIdentifier": self.tango_account_id,
"customerIdentifier": self.tango_customer_id,
"utid": utid,
- "amount": str(amount),
+ "amount": amount_in_currency,
"campaign": "300large",
"sendEmail": False,
"externalRefID": payout_event_id,
diff --git a/generalresearch/managers/thl/wallet/user_payout.py b/generalresearch/managers/thl/wallet/user_payout.py
index a1c628b..983ab3a 100644
--- a/generalresearch/managers/thl/wallet/user_payout.py
+++ b/generalresearch/managers/thl/wallet/user_payout.py
@@ -68,8 +68,8 @@ class UserPayoutEventManager(PayoutEventManager):
return pe
def get_payout_detail(self, pe_uuid: UUIDStr) -> CashoutRequestInfo:
- # This gets the payout event, and then extracts information for
- # the purposes of returning to the user.
+ # This gets the payout event and then extracts information to return
+ # to the user.
pe = self.get_by_uuid(pe_uuid=pe_uuid)
transaction_info = {}
@@ -205,9 +205,6 @@ class UserPayoutEventManager(PayoutEventManager):
created: AwareDatetimeISO | None = None,
request_data: dict[str, Any] | None = None,
# --- Optional: None ---
- account_reference_type: str | None = None,
- account_reference_uuid: UUIDStr | None = None,
- description: str | None = None,
ext_ref_id: str | None = None,
order_data: dict[str, Any] | CashMailOrderData | None = None,
) -> UserPayoutEvent:
@@ -215,10 +212,7 @@ class UserPayoutEventManager(PayoutEventManager):
payout_event = UserPayoutEvent(
uuid=uuid or uuid4().hex,
debit_account_uuid=debit_account_uuid,
- account_reference_type=account_reference_type,
- account_reference_uuid=account_reference_uuid,
cashout_method_uuid=cashout_method_uuid,
- description=description,
created=created or datetime.now(tz=UTC),
amount=amount,
status=status or PayoutStatus.PENDING,
@@ -250,7 +244,7 @@ class UserPayoutEventManager(PayoutEventManager):
assert c.rowcount == 1, f"expected 1 row inserted, got {c.rowcount}"
conn.commit()
- return payout_event
+ return self.get_by_uuid(payout_event.uuid)
def try_user_request_redeem(
self,
@@ -261,7 +255,6 @@ class UserPayoutEventManager(PayoutEventManager):
cashout_method_manager: CashoutMethodManager,
ledger_manager: ThlLedgerManager,
user_ip_history_manager: UserIpHistoryManager,
- geoip_info_manager: GeoIpInfoManager,
redis_client: Redis,
slack_client: slack.WebClient | None = None,
) -> tuple[UserPayoutEvent | None, str | None]:
@@ -274,7 +267,6 @@ class UserPayoutEventManager(PayoutEventManager):
cashout_method_manager=cashout_method_manager,
ledger_manager=ledger_manager,
user_ip_history_manager=user_ip_history_manager,
- geoip_info_manager=geoip_info_manager,
redis_client=redis_client,
slack_client=slack_client,
), None
@@ -290,7 +282,6 @@ class UserPayoutEventManager(PayoutEventManager):
cashout_method_manager: CashoutMethodManager,
ledger_manager: ThlLedgerManager,
user_ip_history_manager: UserIpHistoryManager,
- geoip_info_manager: GeoIpInfoManager,
redis_client: Redis,
slack_client: slack.WebClient | None = None,
) -> UserPayoutEvent:
@@ -303,9 +294,7 @@ class UserPayoutEventManager(PayoutEventManager):
now = datetime.now(tz=UTC)
user.prefetch_product(pg_config=self.pg_config)
- country_iso = user_ip_history_manager.get_user_latest_country(
- user, geoip_info_manager
- )
+ country_iso = user_ip_history_manager.get_user_latest_country(user)
assert country_iso, "user has no country"
usd_exchange_rates = tango_manager.get_exchange_rates()
@@ -345,9 +334,7 @@ class UserPayoutEventManager(PayoutEventManager):
product = user.product
banned_countries = user.product.user_health_config.banned_countries
- assert not user_ip_history_manager.is_user_anonymous(
- user, geoip_info_manager=geoip_info_manager
- ), "Anonymous user requesting redemption"
+ assert not user_ip_history_manager.is_user_anonymous(user), "Anonymous user requesting redemption"
if country_iso in banned_countries:
raise AssertionError("Banned country requesting redemption")
diff --git a/generalresearch/models/thl/wallet/cashout_method.py b/generalresearch/models/thl/wallet/cashout_method.py
index a82fa31..61a8992 100644
--- a/generalresearch/models/thl/wallet/cashout_method.py
+++ b/generalresearch/models/thl/wallet/cashout_method.py
@@ -3,6 +3,7 @@ from __future__ import annotations
import hashlib
import logging
from datetime import UTC, datetime
+from decimal import Decimal
from enum import StrEnum
from typing import Any, Literal, Self
@@ -240,7 +241,8 @@ class TangoCashoutMethodRequestData(BaseModel):
accountIdentifier: str = Field()
customerIdentifier: str = Field()
utid: str = Field(description="tango utid")
- amount: USDCent = Field(description="Amount to be paid out")
+ # This is not necessarily in USD. It is in whatever currency the card is in
+ amount: Decimal = Field(description="Amount to be paid out (in USD, EUR, etc)")
campaign: Literal["300large"]
sendEmail: bool = Field(default=False)
externalRefID: str = Field(description="External Ref ID")
diff --git a/test_utils/managers/conftest.py b/test_utils/managers/conftest.py
index 72be79e..a9f92a2 100644
--- a/test_utils/managers/conftest.py
+++ b/test_utils/managers/conftest.py
@@ -25,7 +25,6 @@ from generalresearch.managers.thl.user_streak import (
)
from generalresearch.managers.thl.userhealth import (
AuditLogManager,
- IPRecordManager,
UserIpHistoryManager,
)
from generalresearch.models.definitions import Source
@@ -57,24 +56,6 @@ def audit_log_manager(thl_web_rw: PostgresConfig) -> AuditLogManager:
@pytest.fixture
-def ip_record_manager(
- thl_web_rw: PostgresConfig,
- thl_redis_config: RedisConfig,
- geoip_info_manager: GeoIpInfoManager,
-) -> IPRecordManager:
- assert thl_web_rw.dsn.path
- assert "/unittest-" in thl_web_rw.dsn.path
-
- from generalresearch.managers.thl.userhealth import IPRecordManager
-
- return IPRecordManager(
- pg_config=thl_web_rw,
- redis_config=thl_redis_config,
- geoip_info_manager=geoip_info_manager,
- )
-
-
-@pytest.fixture
def user_iphistory_manager(
thl_web_rw: PostgresConfig,
thl_redis_config: RedisConfig,
@@ -124,6 +105,11 @@ def grip_lookup_results() -> dict[str, GRIPMMDBLookupResult]:
network_operator="Cloudflare",
),
),
+ "2.2.2.2": GRIPMMDBLookupResult(
+ country=GRIPCountryRecord(country_iso="IT"),
+ anonymous=GRIPAnonymousRecord(is_anonymous=False),
+ asn=GRIPAsnRecord(),
+ ),
},
)
@@ -214,7 +200,6 @@ def random_ext_id_factory(base: str = "U02") -> Callable[..., str]:
@pytest.fixture(scope="session")
def example_tango_cashout_methods(
- random_ext_id_factory: Callable[..., str],
) -> list[CashoutMethod]:
return [
CashoutMethod(
diff --git a/test_utils/managers/thl/conftest.py b/test_utils/managers/thl/conftest.py
index 5b96680..2dce648 100644
--- a/test_utils/managers/thl/conftest.py
+++ b/test_utils/managers/thl/conftest.py
@@ -40,7 +40,6 @@ if TYPE_CHECKING:
UserMetadataManager,
)
from generalresearch.managers.thl.userhealth import (
- AuditLogManager,
IPRecordManager,
)
from generalresearch.managers.thl.wall import (
@@ -162,13 +161,6 @@ def brokerage_product_payout_event_manager(
)
-@pytest.fixture()
-def audit_log_manager(thl_web_rw: PostgresConfig) -> AuditLogManager:
- from generalresearch.managers.thl.userhealth import AuditLogManager
-
- return AuditLogManager(pg_config=thl_web_rw)
-
-
@pytest.fixture(scope="session")
def business_payout_event_manager(
thl_web_rw: PostgresConfig, thl_redis_config: RedisConfig
@@ -338,9 +330,6 @@ def surveypenalty_manager(thl_redis_config: RedisConfig):
return SurveyPenaltyManager(redis_config=thl_redis_config)
-# --- IP Record ---
-
-
@pytest.fixture(scope="session")
def ip_record_manager(
thl_web_rw: PostgresConfig, thl_redis_config: RedisConfig
diff --git a/tests/managers/thl/test_ledger/test_thl_pem.py b/tests/managers/thl/test_ledger/test_thl_pem.py
index 4761438..2d03b25 100644
--- a/tests/managers/thl/test_ledger/test_thl_pem.py
+++ b/tests/managers/thl/test_ledger/test_thl_pem.py
@@ -122,38 +122,6 @@ class TestThlPayoutEventManager:
assert len(res) == (N_PRODUCTS * N_PAYOUT_EVENTS)
assert sum([i.amount for i in res]) == sum(amounts)
- @pytest.mark.skip
- def test_get_payout_detail(self, user_payout_event_manager: UserPayoutEventManager):
- """This fails because the description coming back is None, but then
- it tries to return a PayoutEvent which validates that the
- description can't be None
- """
- from generalresearch.models.thl.payout import (
- PayoutType,
- )
-
- rand_amount = randint(a=99, b=999)
-
- pe = user_payout_event_manager.create(
- debit_account_uuid=uuid4().hex,
- account_reference_type="str-type-random",
- account_reference_uuid=uuid4().hex,
- cashout_method_uuid=uuid4().hex,
- description="Best payout !",
- amount=rand_amount,
- status=PayoutStatus.PENDING,
- ext_ref_id="123",
- payout_type=PayoutType.CASH_IN_MAIL,
- request_data={"foo": 123},
- order_data={},
- )
-
- res = user_payout_event_manager.get_payout_detail(pe_uuid=pe.uuid)
- assert isinstance(res, CashoutRequestInfo)
-
- # def test_filter_by(self):
- # raise NotImplementedError
-
def test_create(
self,
user_payout_event_factory: Callable[..., UserPayoutEvent],
diff --git a/tests/managers/thl/test_payout.py b/tests/managers/thl/test_payout.py
index 69b9737..a9cd52a 100644
--- a/tests/managers/thl/test_payout.py
+++ b/tests/managers/thl/test_payout.py
@@ -34,9 +34,9 @@ if TYPE_CHECKING:
BrokerageProductPayoutEventManager,
BusinessPayoutEventManager,
PayoutEventManager,
- UserPayoutEventManager,
)
from generalresearch.managers.thl.product import ProductManager
+ from generalresearch.managers.thl.wallet.user_payout import UserPayoutEventManager
from generalresearch.models.gr.business import Business
from generalresearch.models.thl.ledger import LedgerAccount
from generalresearch.models.thl.payout import (
diff --git a/tests/managers/thl/test_wallet/__init__.py b/tests/managers/thl/test_wallet/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/managers/thl/test_wallet/__init__.py
diff --git a/tests/managers/thl/test_wallet/test_user_payout.py b/tests/managers/thl/test_wallet/test_user_payout.py
new file mode 100644
index 0000000..bbc97a3
--- /dev/null
+++ b/tests/managers/thl/test_wallet/test_user_payout.py
@@ -0,0 +1,435 @@
+from datetime import datetime
+from decimal import Decimal
+from random import randint
+from unittest.mock import Mock
+from uuid import uuid4
+
+import pytest
+from faker import Faker
+
+from generalresearch.currency import USDCent
+from generalresearch.managers.thl.cashout_method import CashoutMethodManager
+from generalresearch.managers.thl.ipinfo import GeoIpInfoManager
+from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager
+from generalresearch.managers.thl.userhealth import UserIpHistoryManager
+from generalresearch.managers.thl.wallet.tango import TangoManager
+from generalresearch.managers.thl.wallet.user_payout import (
+ UserPayoutEventManager,
+ make_request_paypal,
+)
+from generalresearch.models.thl.definitions import PayoutStatus
+from generalresearch.models.thl.payout import UserPayoutEvent
+from generalresearch.models.thl.user import User
+from generalresearch.models.thl.wallet.cashout_method import (
+ CashMailCashoutMethodData,
+ CashMailCashoutMethodRequestData,
+ CashoutRequestInfo,
+ PaypalCashoutMethodData,
+ PaypalCashoutMethodRequestData,
+ USDeliveryAddress,
+)
+from generalresearch.models.thl.wallet.definitions import PayoutType
+
+fake = Faker()
+
+
+class TestUserPayoutEventManager:
+ def test_get_by_uuid_and_create(
+ self,
+ user: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ thl_ledger_manager: ThlLedgerManager,
+ utc_now: datetime,
+ ):
+ user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user)
+ request_data = PaypalCashoutMethodRequestData.model_validate(
+ {
+ "email": fake.email(),
+ "interface": "api",
+ }
+ )
+ pe1: UserPayoutEvent = user_payout_event_manager.create(
+ debit_account_uuid=user_account.uuid,
+ payout_type=PayoutType.PAYPAL,
+ cashout_method_uuid=uuid4().hex,
+ amount=100,
+ created=utc_now,
+ request_data=request_data.model_dump(mode="json"),
+ )
+ # these get added by the query
+ pe1.account_reference_type = "user"
+ pe1.account_reference_uuid = user.uuid
+
+ pe2 = user_payout_event_manager.get_by_uuid(pe_uuid=pe1.uuid)
+
+ assert pe1 == pe2
+
+ def test_get_payout_detail_cash_in_mail(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ ):
+ user = user_with_wallet
+ data = CashMailCashoutMethodData(
+ delivery_address=USDeliveryAddress.model_validate(
+ {
+ "name_or_attn": "Josh Ackerman",
+ "address": "123 Fake St",
+ "city": "San Francisco",
+ "state": "CA",
+ "postal_code": "12345",
+ }
+ )
+ )
+ cm = cashout_method_manager.create_cash_in_mail_cashout_method(
+ data=data, user=user
+ )
+ user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user)
+
+ rand_amount = randint(a=99, b=999)
+ request_data = CashMailCashoutMethodRequestData.model_validate(
+ cm.data.model_dump()
+ )
+
+ pe = user_payout_event_manager.create(
+ debit_account_uuid=user_account.uuid,
+ cashout_method_uuid=cm.id,
+ amount=rand_amount,
+ ext_ref_id=uuid4().hex,
+ payout_type=PayoutType.CASH_IN_MAIL,
+ request_data=request_data.model_dump(mode="json"),
+ )
+
+ res = user_payout_event_manager.get_payout_detail(pe_uuid=pe.uuid)
+ assert isinstance(res, CashoutRequestInfo)
+
+ def test_get_payout_detail_paypal(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ ):
+ user = user_with_wallet
+ data = PaypalCashoutMethodData(email=fake.email())
+ cm = cashout_method_manager.create_paypal_cashout_method(data=data, user=user)
+ user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user)
+
+ rand_amount = randint(a=99, b=999)
+ request_data = make_request_paypal(cm)
+ pe = user_payout_event_manager.create(
+ debit_account_uuid=user_account.uuid,
+ cashout_method_uuid=cm.id,
+ amount=rand_amount,
+ ext_ref_id=uuid4().hex,
+ payout_type=PayoutType.PAYPAL,
+ request_data=request_data.model_dump(mode="json"),
+ )
+
+ res = user_payout_event_manager.get_payout_detail(pe_uuid=pe.uuid)
+ assert isinstance(res, CashoutRequestInfo)
+
+
+class TestUserRequestRedeem:
+ @staticmethod
+ def request_paypal_payout(
+ *,
+ user: User,
+ amount: USDCent,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ip: str = "8.8.8.8",
+ fund_amount: Decimal = Decimal("10.00"),
+ ) -> UserPayoutEvent:
+ email = fake.email()
+ cashout_method = cashout_method_manager.create_paypal_cashout_method(
+ data=PaypalCashoutMethodData(email=email),
+ user=user,
+ )
+ ip_record_factory(user=user, ip=ip)
+ thl_ledger_manager.create_tx_user_bonus(
+ user=user,
+ amount=fund_amount,
+ ref_uuid=uuid4().hex,
+ description="Fund PayPal redemption test",
+ )
+
+ tango_manager = Mock(spec=TangoManager)
+ tango_manager.get_exchange_rates.return_value = {}
+
+ return user_payout_event_manager.user_request_redeem(
+ user=user,
+ cashout_method_id=cashout_method.id,
+ amount=amount,
+ tango_manager=tango_manager,
+ cashout_method_manager=cashout_method_manager,
+ ledger_manager=thl_ledger_manager,
+ user_ip_history_manager=user_iphistory_manager,
+ redis_client=thl_redis_client,
+ )
+
+ def test_user_request_redeem_paypal(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ geoip_info_manager: GeoIpInfoManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ user = user_with_wallet
+ payout = self.request_paypal_payout(
+ user=user,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ assert payout.status == PayoutStatus.PENDING
+ assert payout.payout_type == PayoutType.PAYPAL
+ assert payout.amount == 600
+ assert payout.request_data["interface"] == "api"
+ assert thl_ledger_manager.get_user_wallet_balance(user) == 400
+ assert user_payout_event_manager.get_by_uuid(payout.uuid) == payout
+
+ detail = user_payout_event_manager.get_payout_detail(payout.uuid)
+ assert not detail.transaction_info
+ assert detail.description == "PayPal"
+
+ def test_user_request_redeem_paypal_complete(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ geoip_info_manager: GeoIpInfoManager,
+ ip_record_factory,
+ thl_redis_client,
+ create_main_accounts,
+ ):
+ create_main_accounts()
+ user = user_with_wallet
+ payout = self.request_paypal_payout(
+ user=user,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.APPROVED,
+ ext_ref_id="paypal-batch-id",
+ )
+ thl_ledger_manager.create_tx_user_payout_complete(
+ user=user,
+ payout_event=payout,
+ fee_amount=Decimal("0.25"),
+ )
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.COMPLETE,
+ order_data={"transaction_id": "paypal-transaction-id"},
+ )
+
+ detail = user_payout_event_manager.get_payout_detail(pe_uuid=payout.uuid)
+ assert detail.status == PayoutStatus.COMPLETE
+ assert detail.transaction_info == {"transaction_id": "paypal-transaction-id"}
+
+ assert thl_ledger_manager.get_user_wallet_balance(user_with_wallet) == 400
+ bp_expense = thl_ledger_manager.get_account_or_create_bp_expense_by_uuid(
+ product_uuid=user.to_user_ref().product_id, expense_name="paypal"
+ )
+ assert thl_ledger_manager.get_account_balance(bp_expense) == -25
+
+ def test_user_request_redeem_tango_complete(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ create_main_accounts,
+ setup_cashoutmethod_db,
+ example_tango_cashout_methods,
+ ):
+ create_main_accounts()
+ setup_cashoutmethod_db()
+ user = user_with_wallet
+ # This is the italian Amazon.it gift card
+ cashout_method = example_tango_cashout_methods[1]
+ # This is an IP that we have hard-coded by the geoip_info_manager as Italy
+ ip_record_factory(user=user, ip="2.2.2.2")
+
+ thl_ledger_manager.create_tx_user_bonus(
+ user=user,
+ amount=Decimal("5.00"),
+ ref_uuid=uuid4().hex,
+ description="Fund Tango redemption test",
+ )
+
+ tango_client = Mock()
+ tango_client.get_exchange_rates.return_value = {
+ "exchangeRates": [
+ {"rewardCurrency": "USD", "baseCurrency": "EUR", "baseFx": 1.14792}
+ ]
+ }
+ tango_manager = TangoManager(
+ tango_client=tango_client,
+ tango_account_id="test-account",
+ tango_customer_id="test-customer",
+ cashout_method_manager=cashout_method_manager,
+ )
+ request = tango_manager.make_request(
+ amount=USDCent(2_00),
+ cashout_method=cashout_method,
+ payout_event_id=uuid4().hex,
+ )
+ assert request.amount == Decimal(2.0 / 1.14792).quantize(
+ Decimal("0.01")
+ ) # ~ 1.74 EUR
+
+ payout = user_payout_event_manager.user_request_redeem(
+ user=user,
+ cashout_method_id=cashout_method.id,
+ amount=USDCent(2_00),
+ tango_manager=tango_manager,
+ cashout_method_manager=cashout_method_manager,
+ ledger_manager=thl_ledger_manager,
+ user_ip_history_manager=user_iphistory_manager,
+ redis_client=thl_redis_client,
+ )
+ assert payout.request_data["amount"] == '1.74'
+ assert payout.request_data["externalRefID"] == payout.uuid
+
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.APPROVED,
+ ext_ref_id="tango-order-id",
+ )
+ thl_ledger_manager.create_tx_user_payout_complete(
+ user=user,
+ payout_event=payout,
+ )
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.COMPLETE,
+ order_data={
+ "reward": {
+ "credentialList": [
+ {
+ "credentialType": "giftCardCode",
+ "credentialValue": "TEST-CODE",
+ }
+ ],
+ "redemptionInstructions": "Use this code at checkout.",
+ }
+ },
+ )
+
+ detail = user_payout_event_manager.get_payout_detail(pe_uuid=payout.uuid)
+ assert payout.payout_type == PayoutType.TANGO
+ assert detail.status == PayoutStatus.COMPLETE
+ assert detail.transaction_info == {
+ "credential_list": [
+ {
+ "credentialType": "giftCardCode",
+ "credentialValue": "TEST-CODE",
+ }
+ ],
+ "redemption_instructions": "Use this code at checkout.",
+ }
+ assert thl_ledger_manager.get_user_wallet_balance(user) == 300
+ bp_expense = thl_ledger_manager.get_account_or_create_bp_expense_by_uuid(
+ product_uuid=user.to_user_ref().product_id,
+ expense_name="tango",
+ )
+ assert thl_ledger_manager.get_account_balance(bp_expense) == -7
+
+ def test_user_request_redeem_paypal_insufficient_balance(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ with pytest.raises(AssertionError, match="more than their redeemable balance"):
+ self.request_paypal_payout(
+ user=user_with_wallet,
+ amount=USDCent(1_100),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ def test_user_request_redeem_paypal_blocked_user(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ user_with_wallet.blocked = True
+
+ with pytest.raises(AssertionError, match="Blocked user"):
+ self.request_paypal_payout(
+ user=user_with_wallet,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ def test_user_request_redeem_paypal_anonymous_user(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ with pytest.raises(AssertionError, match="Anonymous user"):
+ self.request_paypal_payout(
+ user=user_with_wallet,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ ip="1.1.1.1",
+ )