|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
5 | 5 | import datetime as dt |
| 6 | +import logging |
6 | 7 | from typing import Annotated, Any |
7 | 8 | from urllib.parse import unquote |
8 | 9 |
|
| 10 | +logger = logging.getLogger(__name__) |
| 11 | + |
9 | 12 | from fastapi import APIRouter, Depends, HTTPException, Query, status |
10 | 13 | from pydantic import BaseModel, Field |
11 | 14 | from sqlalchemy.orm import Session |
@@ -83,18 +86,16 @@ def ebay_authorize_url( |
83 | 86 | force_login: Annotated[bool, Query()] = False, |
84 | 87 | ) -> dict[str, str]: |
85 | 88 | """Build the browser URL for eBay consent (User must open it).""" |
86 | | - app = get_settings() |
87 | 89 | try: |
88 | | - url = build_authorization_url(state=state, force_login=force_login, app=app) |
| 90 | + url = build_authorization_url(state=state, force_login=force_login) |
89 | 91 | except RuntimeError as exc: |
90 | 92 | if str(exc) == "ebay_oauth_not_configured": |
91 | 93 | raise HTTPException( |
92 | 94 | status_code=status.HTTP_503_SERVICE_UNAVAILABLE, |
93 | 95 | detail="eBay OAuth is not configured on the server (EBAY_CLIENT_ID, EBAY_CLIENT_SECRET, EBAY_REDIRECT_URI).", |
94 | 96 | ) from exc |
95 | 97 | raise HTTPException(status_code=500, detail=str(exc)) from exc |
96 | | - redirect = (app.ebay_redirect_uri or "").strip() |
97 | | - return {"authorization_url": url, "state": state, "redirect_uri": redirect} |
| 98 | + return {"authorization_url": url, "state": state} |
98 | 99 |
|
99 | 100 |
|
100 | 101 | @router.post("/oauth/exchange", status_code=status.HTTP_200_OK) |
@@ -135,12 +136,36 @@ async def ebay_oauth_exchange( |
135 | 136 | db.add(user) |
136 | 137 | db.commit() |
137 | 138 | db.refresh(user) |
138 | | - scope = token_scope_string(data) |
| 139 | + |
| 140 | + # Auth-code exchange often omits ``scope``; refresh with all consented scopes so the access |
| 141 | + # token includes sell.fulfillment (required for getOrders / shipping labels). |
| 142 | + scope_data = data |
| 143 | + rt_plain = decrypt_ebay_token(user.ebay_refresh_token) |
| 144 | + if rt_plain: |
| 145 | + try: |
| 146 | + scope_data = await refresh_user_access_token(rt_plain, request_all_scopes=True) |
| 147 | + access = scope_data.get("access_token") |
| 148 | + if access and isinstance(access, str): |
| 149 | + user.ebay_access_token = store_ebay_token(access) |
| 150 | + expires_in = int(scope_data.get("expires_in", 7200)) |
| 151 | + user.ebay_access_expires_at = now + dt.timedelta(seconds=expires_in) |
| 152 | + if scope_data.get("refresh_token"): |
| 153 | + user.ebay_refresh_token = store_ebay_token(str(scope_data["refresh_token"])) |
| 154 | + db.add(user) |
| 155 | + db.commit() |
| 156 | + db.refresh(user) |
| 157 | + except Exception as exc: |
| 158 | + logger.warning("eBay post-exchange refresh failed (scopes may be incomplete): %s", exc) |
| 159 | + |
| 160 | + scope = token_scope_string(scope_data) |
| 161 | + has_fulfillment = token_has_fulfillment_scope(scope_data) or ( |
| 162 | + not scope and bool(decrypt_ebay_token(user.ebay_refresh_token)) |
| 163 | + ) |
139 | 164 | return { |
140 | 165 | "ok": True, |
141 | 166 | "ebay_connected": bool(decrypt_ebay_token(user.ebay_refresh_token)), |
142 | 167 | "oauth_scope": scope, |
143 | | - "has_fulfillment_scope": token_has_fulfillment_scope(data), |
| 168 | + "has_fulfillment_scope": has_fulfillment, |
144 | 169 | } |
145 | 170 |
|
146 | 171 |
|
|
0 commit comments