FKN SHITSTATION AND THEIR GODDAMN SANDBOX BS - Sandbox will be it's own separate entity

This commit is contained in:
2026-09-16 16:24:20 -05:00
parent b5570d39f0
commit 82e6148ca9
4 changed files with 531 additions and 20 deletions
+111 -7
View File
@@ -81,18 +81,44 @@ def list_carriers() -> List[dict]:
def list_stores() -> List[dict]:
"""
Same idea as list_carriers() but for GET /v2/stores - the test/sandbox
account almost certainly has different store IDs than production too
(confirmed the same is true for carriers), so this is worth checking
before it becomes the next "not found" error rather than after.
DEAD END, kept only so nobody re-attempts this the hard way: ShipStation's
V2 API has NO stores/marketplaces listing endpoint. GET /v2/stores returns
a plain 404 ("No route matched with those values") - confirmed against
ShipStation's own V2 OpenAPI reference, which lists every real section
(Carriers, Warehouses, Connections, etc.) and where store_id only ever
appears as an INPUT field on label/shipment requests (example value
"se-12345"), never as its own resource with a list/get endpoint. This
matches ShipStation's help docs too, which say the only ways to get a
store_id are (1) ShipStation support looks it up for you, or (2) the
legacy V1 API's List Stores call - different auth (key+secret Basic
Auth), not the single V2 API-Key header this app uses everywhere else.
Bottom line: there is no way to discover a store_id from inside this
app. Log into the ShipStation account's own UI (Settings > Store Setup
/ Selling Channels) to find or create a manual store and read its ID
from there - for test mode specifically, that means logging into the
TEST/sandbox account, not production.
"""
raise ShipStationSendError(
"ShipStation's V2 API has no endpoint to list stores (confirmed - GET /v2/stores "
"doesn't exist, it 404s). Log into the ShipStation account's own UI under "
"Settings > Store Setup to find the store_id, then enter it in Settings - for "
"test mode, log into the TEST/sandbox account and set the TEST_ override."
)
def list_warehouses() -> List[dict]:
"""Same idea as list_carriers()/list_stores() but for GET /v2/warehouses -
fixes the exact next error in the sequence (warehouse_id not found),
same root cause as the carrier_id one: the test/sandbox account is a
completely separate ShipStation environment with its own IDs."""
api_key = config.get_shipstation_setting("SHIPSTATION_API_KEY")
if not api_key:
raise ShipStationSendError("ShipStation API Key is not set. Add it in Settings.")
try:
response = requests.get(
f"{API_BASE}/stores",
f"{API_BASE}/warehouses",
headers={"API-Key": api_key, "Accept": "application/json"},
timeout=REQUEST_TIMEOUT_SECONDS,
)
@@ -111,8 +137,86 @@ def list_stores() -> List[dict]:
except ValueError as exc:
raise ShipStationSendError("ShipStation returned a response that wasn't valid JSON.") from exc
# Some ShipStation accounts return a bare list, others wrap it - handle both.
return data if isinstance(data, list) else data.get("stores", [])
return data if isinstance(data, list) else data.get("warehouses", [])
def create_warehouse(name: str, origin_address: dict) -> dict:
"""POST /v2/warehouses - creates a new warehouse for whichever API key
is currently active. Needed because a sandbox/test ShipStation account
starts with zero warehouses (confirmed against ShipStation's own docs):
list_warehouses() correctly returning an empty list in test mode wasn't
a bug, there was just nothing there yet to list. Returns the created
warehouse's JSON (includes warehouse_id - the value that goes into
TEST_SHIPSTATION_SIGNIFY_WAREHOUSE_ID / TEST_SHIPSTATION_OAKSTREET_WAREHOUSE_ID)."""
api_key = config.get_shipstation_setting("SHIPSTATION_API_KEY")
if not api_key:
raise ShipStationSendError("ShipStation API Key is not set. Add it in Settings.")
payload = {"name": name, "origin_address": origin_address}
try:
response = requests.post(
f"{API_BASE}/warehouses",
json=payload,
headers={"API-Key": api_key, "Accept": "application/json"},
timeout=REQUEST_TIMEOUT_SECONDS,
)
except requests.RequestException as exc:
raise ShipStationSendError(f"Could not reach ShipStation: {exc}") from exc
if response.status_code == 401:
raise ShipStationSendError("ShipStation rejected the API key (401). Check it in Settings.")
if not response.ok:
raise ShipStationSendError(
f"ShipStation returned an error ({response.status_code}): {response.text[:400]}"
)
try:
return response.json()
except ValueError as exc:
raise ShipStationSendError("ShipStation returned a response that wasn't valid JSON.") from exc
def _origin_address_for_company(company: str) -> dict:
"""Same physical address as _return_address_for_order's ship-from/
ship-to address (SIGNIFY_RETURN_*/OAKSTREET_RETURN_* settings) - a
warehouse's origin_address is just that address registered with
ShipStation. address_residential_indicator is required by
POST /v2/warehouses; this is always a business address."""
settings = config.load_settings()
prefix = "SIGNIFY_RETURN_" if company == "Signify Health" else "OAKSTREET_RETURN_"
return {
"name": settings.get(f"{prefix}NAME", ""),
"phone": settings.get(f"{prefix}PHONE", ""),
"company_name": company,
"address_line1": settings.get(f"{prefix}ADDRESS1", ""),
"address_line2": settings.get(f"{prefix}ADDRESS2", "") or None,
"city_locality": settings.get(f"{prefix}CITY", ""),
"state_province": settings.get(f"{prefix}STATE", ""),
"postal_code": settings.get(f"{prefix}ZIP", ""),
"country_code": "US",
"address_residential_indicator": "no",
}
def create_test_warehouse_for_company(company: str) -> dict:
"""Creates a warehouse for whichever API key is currently active
(callers should confirm test mode is on first - this isn't something
you want to accidentally run against production) using the company's
own return-address settings as the origin_address. Returns the created
warehouse's JSON."""
origin_address = _origin_address_for_company(company)
required_fields = (
"name", "phone", "address_line1", "city_locality", "state_province", "postal_code",
)
missing = [field for field in required_fields if not origin_address.get(field)]
if missing:
prefix = "SIGNIFY_RETURN_" if company == "Signify Health" else "OAKSTREET_RETURN_"
raise ShipStationSendError(
f"{prefix}* address settings for {company} are incomplete (missing: "
f"{', '.join(missing)}). Fill those in under Settings > Return Labels first."
)
return create_warehouse(f"{company} (Test)", origin_address)
# Column order confirmed against the real ShipStation upload template
# (OAK.csv) - "COPY ME ALREADY" is a spreadsheet-only helper column and