aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstuppie2026-09-22 15:56:14 -0600
committerstuppie2026-09-22 15:56:14 -0600
commitb1af1375578e41a240a4eedff73209e212a31051 (patch)
treef665fe772a981b5c69b00da0587894ed9ada85a9
parentb63021c4492a1742b8fa65c39ec648f140e0748b (diff)
downloadamt-jb-b1af1375578e41a240a4eedff73209e212a31051.tar.gz
amt-jb-b1af1375578e41a240a4eedff73209e212a31051.zip
create_paypal_cashout_method upon user login. fix nginx for docs
-rw-r--r--jb-ui/src/pages/CashoutConfirmation.tsx41
-rw-r--r--jb/managers/thl.py37
-rw-r--r--jb/views/auth.py14
-rw-r--r--jb/views/wallet.py1
-rw-r--r--nginx_amt-jb.conf10
5 files changed, 103 insertions, 0 deletions
diff --git a/jb-ui/src/pages/CashoutConfirmation.tsx b/jb-ui/src/pages/CashoutConfirmation.tsx
new file mode 100644
index 0000000..6964672
--- /dev/null
+++ b/jb-ui/src/pages/CashoutConfirmation.tsx
@@ -0,0 +1,41 @@
+import { useEffect, useRef, useState } from "react";
+
+type ConfirmationState = "working" | "confirmed" | "failed" | "missing";
+
+const CashoutConfirmation = function () {
+ const started = useRef(false);
+ const [state, setState] = useState<ConfirmationState>("working");
+
+ useEffect(() => {
+ if (started.current) return;
+ started.current = true;
+
+ const params = new URLSearchParams(window.location.search);
+ const token = params.get("token");
+ if (!token) {
+ setState("missing");
+ return;
+ }
+
+ window.history.replaceState({}, "", window.location.pathname);
+
+ void fetch("/wallet/cashout/confirm/", {
+ method: "POST",
+ credentials: "include",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ token }),
+ })
+ .then((response) => {
+ if (!response.ok) throw new Error("Cashout confirmation failed");
+ setState("confirmed");
+ })
+ .catch(() => setState("failed"));
+ }, []);
+
+ if (state === "missing") return <p>This cashout link is missing its token.</p>;
+ if (state === "failed") return <p>This cashout link is invalid or has expired.</p>;
+ if (state === "confirmed") return <p>Your cashout has been confirmed.</p>;
+ return <p>Confirming your cashout…</p>;
+};
+
+export default CashoutConfirmation;
diff --git a/jb/managers/thl.py b/jb/managers/thl.py
index f88acad..59bd2ab 100644
--- a/jb/managers/thl.py
+++ b/jb/managers/thl.py
@@ -10,6 +10,8 @@ from generalresearch.models.thl.wallet.cashout_method import (
)
from jb.config import settings
+
+
def get_task_status(tsid: str) -> TaskStatusResponse | None:
url = f"{settings.fsb_host}{settings.product_id}/status/{tsid}/"
d = requests.get(url).json()
@@ -61,6 +63,41 @@ def manage_pending_cashout(
return UserPayoutEvent.model_validate(d)
+def get_paypal_cashout_method_if_exists(product_user_id: str) -> str | None:
+ """
+ Todo: This does not work right now for new users, in that this endpoint expects the user
+ to have IP history or the request made from the client
+ (b/c it needs their country to know which tango card are available).
+ We don't care about that for paypal, but, it expects it anyways.
+ Once a user can change their paypal email, this needs to change to avoid overwriting it
+ every time they login.
+ """
+ url = f"{settings.fsb_host}{settings.product_id}/cashout_methods/"
+ params = {"bpuid": product_user_id}
+ res = requests.get(url, params=params)
+ assert res.status_code == status.HTTP_200_OK, res.text
+ cms = res.json()["cashout_methods"]
+ res = next(filter(lambda x: x["type"] == "PAYPAL", cms), None)
+ if res:
+ return res["id"]
+ return None
+
+
+def create_paypal_cashout_method(product_user_id: str, email: str) -> dict:
+ url = f"{settings.fsb_host}{settings.product_id}/cashout_methods/"
+ body = {"bpuid": product_user_id, "type": "PAYPAL", "email": email}
+ res = requests.post(url, json=body)
+ assert res.status_code == status.HTTP_200_OK, res.text
+ return res.json()["cashout_method"]
+
+
+def create_paypal_cashout_method_if_not_exists(
+ product_user_id: str, email: str
+) -> None:
+ if not get_paypal_cashout_method_if_exists(product_user_id):
+ create_paypal_cashout_method(product_user_id, email)
+
+
def get_wallet_balance(amt_worker_id: str) -> USDCent:
# This will raise an Exception if wallet balance is negative
url = f"{settings.fsb_host}{settings.product_id}/wallet/"
diff --git a/jb/views/auth.py b/jb/views/auth.py
index cc1224f..36b013f 100644
--- a/jb/views/auth.py
+++ b/jb/views/auth.py
@@ -26,6 +26,10 @@ from jb.managers.email_manager import (
send_login_email,
)
from jb.managers.gr_api import GRApiManager
+from jb.managers.thl import (
+ create_paypal_cashout_method_if_not_exists,
+ create_paypal_cashout_method,
+)
from jb.models.auth import (
AccountLogin,
AmtAccountLink,
@@ -112,6 +116,14 @@ def exchange_magic_link(
def _exchange_magic_link(token: str, response: Response, gr_api: GRApiManager) -> None:
user_email = consume_magic_token(token)
user = gr_api.ensure_user_exists(User.model_validate({"email": user_email}))
+
+ # create_paypal_cashout_method_if_not_exists(
+ # product_user_id=user.product_user_id, email=user.email
+ # )
+ create_paypal_cashout_method(
+ product_user_id=user.product_user_id, email=user.email
+ )
+
response.set_cookie(
key=SESSION_COOKIE_NAME,
value=create_session(user.product_user_id),
@@ -210,6 +222,8 @@ def _exchange_amt_account_link(token: str, response: Response, gr_api: GRApiMana
user = User(email=email)
user = gr_api.transition_user_from_amt(user=user, amt_worker_id=amt_worker_id)
+ # create_paypal_cashout_method_if_not_exists(product_user_id=user.product_user_id, email=user.email)
+ create_paypal_cashout_method(product_user_id=user.product_user_id, email=user.email)
# In Mautic, associate the email with the worker ID (AFTER the user has transitioned)
get_or_create_contact(email=email, amt_worker_id=amt_worker_id)
diff --git a/jb/views/wallet.py b/jb/views/wallet.py
index 90e8de8..c281698 100644
--- a/jb/views/wallet.py
+++ b/jb/views/wallet.py
@@ -58,6 +58,7 @@ def cashout_confirmation_page() -> HTMLResponse:
@wallet_router.post("/cashout/confirm/", response_model=CashoutRequestInfo)
def confirm_cashout(
body: CashoutConfirmation,
+ user: Annotated[User, Depends(get_authenticated_user)],
) -> CashoutRequestInfo:
cashout = consume_cashout_token(body.token)
diff --git a/nginx_amt-jb.conf b/nginx_amt-jb.conf
index 6d6947c..0b0f385 100644
--- a/nginx_amt-jb.conf
+++ b/nginx_amt-jb.conf
@@ -30,6 +30,16 @@ server {
return 200 '{"status":"ok"}';
}
+ location = /docs/ {
+ include nginx_amt-jb_proxy_pass.conf;
+ proxy_pass http://uvicorn/docs;
+ }
+
+ location = /redoc/ {
+ include nginx_amt-jb_proxy_pass.conf;
+ proxy_pass http://uvicorn/redoc;
+ }
+
location / {
include nginx_amt-jb_proxy_pass.conf;
proxy_pass http://uvicorn;