diff options
| author | stuppie | 2026-09-22 15:56:14 -0600 |
|---|---|---|
| committer | stuppie | 2026-09-22 15:56:14 -0600 |
| commit | b1af1375578e41a240a4eedff73209e212a31051 (patch) | |
| tree | f665fe772a981b5c69b00da0587894ed9ada85a9 | |
| parent | b63021c4492a1742b8fa65c39ec648f140e0748b (diff) | |
| download | amt-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.tsx | 41 | ||||
| -rw-r--r-- | jb/managers/thl.py | 37 | ||||
| -rw-r--r-- | jb/views/auth.py | 14 | ||||
| -rw-r--r-- | jb/views/wallet.py | 1 | ||||
| -rw-r--r-- | nginx_amt-jb.conf | 10 |
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; |
