Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions compliance/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,18 @@ def _normalize_administrative_area(
}


def _missing_bill_to_fields(bill_to: dict[str, str]) -> list[str]:
"""Return the CyberSource bill-to field names missing from the given data."""
def _missing_bill_to_fields(
bill_to: dict[str, str], *, always_require_postal_and_state: bool = False
) -> list[str]:
"""
Return the CyberSource bill-to field names missing from the given data.

CyberSource itself only requires administrative_area/postal_code for US
and CA addresses. Pass always_require_postal_and_state=True to flag them
as missing whenever they're empty regardless of country - used when
reporting missing profile fields back to the user, since they're useful
to have on file no matter where they live.
"""
missing_fields = []

if not bill_to.get("first_name"):
Expand All @@ -132,7 +142,7 @@ def _missing_bill_to_fields(bill_to: dict[str, str]) -> list[str]:
missing_fields.append("last_name")

required_fields = ["address1", "locality", "country", "email"]
if bill_to.get("country") in {"US", "CA"}:
if always_require_postal_and_state or bill_to.get("country") in {"US", "CA"}:
required_fields.extend(["administrative_area", "postal_code"])

missing_fields.extend(field for field in required_fields if not bill_to.get(field))
Expand Down Expand Up @@ -182,9 +192,17 @@ def _build_bill_to(user) -> dict[str, str]:


def get_missing_export_compliance_fields(user) -> list[str]:
"""Return the profile field names required for an export compliance check that are missing."""
"""
Return the profile field names required for an export compliance check that are missing.

Unlike the CyberSource check itself, state and postal code are always
flagged here when empty, even for countries where CyberSource doesn't
require them.
"""
bill_to = _build_bill_to(user)
missing_bill_to_fields = _missing_bill_to_fields(bill_to)
missing_bill_to_fields = _missing_bill_to_fields(
bill_to, always_require_postal_and_state=True
)
return sorted(
{BILL_TO_FIELD_TO_PROFILE_FIELD[field] for field in missing_bill_to_fields}
)
Expand Down
48 changes: 48 additions & 0 deletions compliance/api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
decrypt_export_compliance_log,
get_cybersource_client,
get_latest_export_compliance_log,
get_missing_export_compliance_fields,
log_export_compliance_check,
verify_user_with_exports,
)
Expand Down Expand Up @@ -106,6 +107,53 @@ def test_build_export_payload_requires_legal_address(export_settings):
]


def test_build_export_payload_does_not_require_state_and_postal_code_outside_us_ca(
export_settings,
):
"""CyberSource itself only requires state/postal code for US and CA addresses."""
user = UserFactory.create(name="Ada Lovelace", email="ada@example.com")
user.legal_address.country = "FR"
user.legal_address.street_address_1 = "5 Rue de Rivoli"
user.legal_address.city = "Paris"
user.legal_address.state = ""
user.legal_address.postal_code = ""
user.legal_address.save()

payload = _build_export_payload(user)

assert payload.order_information.bill_to.country == "FR"


def test_get_missing_export_compliance_fields_always_flags_state_and_postal_code():
"""
The profile-facing missing fields list should flag state/postal code
whenever they're empty, even for countries where CyberSource doesn't
require them.
"""
user = UserFactory.create(name="Ada Lovelace", email="ada@example.com")
user.legal_address.country = "FR"
user.legal_address.street_address_1 = "5 Rue de Rivoli"
user.legal_address.city = "Paris"
user.legal_address.state = ""
user.legal_address.postal_code = ""
user.legal_address.save()

assert get_missing_export_compliance_fields(user) == ["postal_code", "state"]


def test_get_missing_export_compliance_fields_us_address():
"""State/postal code should still be flagged as missing for US addresses."""
user = UserFactory.create(name="Ada Lovelace", email="ada@example.com")
user.legal_address.country = "US"
user.legal_address.street_address_1 = "77 Massachusetts Ave"
user.legal_address.city = "Cambridge"
user.legal_address.state = ""
user.legal_address.postal_code = ""
user.legal_address.save()

assert get_missing_export_compliance_fields(user) == ["postal_code", "state"]


def test_normalize_administrative_area_strips_country_prefix():
"""ISO-3166-2 values should be reduced to the region code for CyberSource."""
assert _normalize_administrative_area("US", "US-MA") == "MA"
Expand Down