FKN SHITSTATION AND THEIR GODDAMN SANDBOX BS - Sandbox will be it's own separate entity
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user