Require a complete PCM return address before placing orders.
Deploy Beta / unit-tests (push) Successful in 14s
Deploy Beta / docker (push) Successful in 17s
Deploy Beta / deploy-beta (push) Successful in 1m40s

Empty PCM_RETURN_ADDRESS was sending SITE_NAME as firstName with blank street fields, which PCM rejects with 400; validate early and wire PCM env through compose.
This commit is contained in:
2026-08-11 05:52:39 -05:00
parent 7fb5317308
commit 2643ba7a96
5 changed files with 126 additions and 28 deletions
+68 -24
View File
@@ -193,38 +193,82 @@ def pcm_request(
def return_address_from_settings() -> dict[str, str]:
"""Build PCM returnAddress from PCM_RETURN_ADDRESS JSON or CONTACT_* vars."""
"""Build PCM returnAddress from PCM_RETURN_ADDRESS JSON and/or PCM_RETURN_* vars.
PCM requires address/city/state (and zip) whenever firstName is set — an empty
street with SITE_NAME-derived firstName causes a 400 from POST /order/postcard.
"""
data: dict[str, Any] = {}
raw = (settings.PCM_RETURN_ADDRESS or "").strip()
if raw:
data = json.loads(raw)
if not isinstance(data, dict):
try:
parsed = json.loads(raw)
except json.JSONDecodeError as exc:
raise PcmApiError(
"PCM_RETURN_ADDRESS must be valid JSON "
'(e.g. {"firstName":"","lastName":"","address":"",'
'"city":"","state":"IL","zipCode":""})'
) from exc
if not isinstance(parsed, dict):
raise PcmApiError("PCM_RETURN_ADDRESS must be a JSON object")
return {
"company": str(data.get("company") or ""),
"firstName": str(data.get("firstName") or data.get("first_name") or ""),
"lastName": str(data.get("lastName") or data.get("last_name") or ""),
"address": str(data.get("address") or data.get("line1") or ""),
"address2": str(data.get("address2") or data.get("line2") or ""),
"city": str(data.get("city") or ""),
"state": str(data.get("state") or ""),
"zipCode": str(data.get("zipCode") or data.get("zip") or ""),
}
data = parsed
name = (settings.SITE_NAME or "").strip()
parts = name.split(None, 1)
first = parts[0] if parts else "Monica"
last = parts[1] if len(parts) > 1 else ""
return {
"company": "",
"firstName": first,
"lastName": last,
"address": str(getattr(settings, "PCM_RETURN_LINE1", "") or ""),
"address2": str(getattr(settings, "PCM_RETURN_LINE2", "") or ""),
"city": str(getattr(settings, "PCM_RETURN_CITY", "") or ""),
"state": str(getattr(settings, "PCM_RETURN_STATE", "") or ""),
"zipCode": str(getattr(settings, "PCM_RETURN_ZIP", "") or ""),
site_first = parts[0] if parts else ""
site_last = parts[1] if len(parts) > 1 else ""
address = {
"company": str(data.get("company") or "").strip(),
"firstName": str(
data.get("firstName") or data.get("first_name") or site_first or ""
).strip(),
"lastName": str(
data.get("lastName") or data.get("last_name") or site_last or ""
).strip(),
"address": str(
data.get("address")
or data.get("line1")
or getattr(settings, "PCM_RETURN_LINE1", "")
or ""
).strip(),
# PCM treats blank address2 oddly; space matches recipient payload.
"address2": str(
data.get("address2")
or data.get("line2")
or getattr(settings, "PCM_RETURN_LINE2", "")
or ""
).strip()
or " ",
"city": str(
data.get("city") or getattr(settings, "PCM_RETURN_CITY", "") or ""
).strip(),
"state": str(
data.get("state") or getattr(settings, "PCM_RETURN_STATE", "") or ""
).strip(),
"zipCode": str(
data.get("zipCode")
or data.get("zip")
or getattr(settings, "PCM_RETURN_ZIP", "")
or ""
).strip(),
}
missing = [
field
for field in ("firstName", "address", "city", "state", "zipCode")
if not address.get(field, "").strip()
]
if missing:
raise PcmApiError(
"PCM return address incomplete (missing "
+ ", ".join(missing)
+ "). Set PCM_RETURN_ADDRESS JSON with firstName, lastName, "
"address, city, state, zipCode "
"(or PCM_RETURN_LINE1 / CITY / STATE / ZIP)."
)
return address
def contact_to_pcm_recipient(contact, *, ext_ref: str) -> dict[str, str]:
address = contact.postal_address or {}