From 75613daba10fe5ef25519fcabb753407b1cffed7 Mon Sep 17 00:00:00 2001 From: julia-truss Date: Wed, 13 May 2026 17:38:42 +0000 Subject: [PATCH] ENG-4513: Sync AuthorizationDTO and RichMerchantData with Unit API docs (#70) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Brings `AuthorizationDTO` and `RichMerchantData` in sync with [Unit's API documentation](https://www.unit.co/docs/api/resources/#authorization). ## Changes ### `AuthorizationDTO` — new fields The following optional fields were missing from `AuthorizationDTO` compared to what Unit's API actually returns: | Field | Type | When present | |-------|------|-------------| | `declineReason` | `str` | When status is `Declined` | | `declineDescription` | `str` | Some declined authorizations | | `declinedBy` | `str` (`Visa`, `Org`, `Unit`) | When status is `Declined` | | `paymentMethod` | `str` (`Manual`, `Swipe`, `Contactless`, `ChipAndPin`, `Stored`, `Other`) | Optional | | `digitalWallet` | `str` (`Google`, `Apple`, `Other`) | Optional | | `cardVerificationData` | `CardVerificationData` | Optional | | `cardNetwork` | `str` (`Visa`, `Interlink`, `Accel`, `Allpoint`, `Other`) | Optional | | `cashWithdrawalAmount` | `int` | Optional | | `summary` | `str` | Optional | | `richMerchantData` | `RichMerchantData` | When transaction enrichment is enabled | | `currencyConversion` | `CurrencyConversion` | When original currency is not USD | ### `RichMerchantData` — bug fix `RichMerchantData.from_json_api` was passing the raw `address` dict through without deserializing it via `RichMerchantDataAddress.from_json_api`. This meant the `address` attribute was a plain dict at runtime instead of a typed `RichMerchantDataAddress` object. Fixed to properly deserialize. ### New type: `CardVerificationData` Added to `unit/models/__init__.py` to represent the nested `cardVerificationData` object with its `verificationMethod` field. ### Style Switched both `AuthorizationDTO` and `RichMerchantData` constructors/factories to use keyword arguments per SDK conventions. ## Backward compatibility All new `AuthorizationDTO` fields are optional with `None` defaults. The constructor signature is compatible with existing callers — the only construction site is `from_json_api` which now uses keyword arguments. The `RichMerchantData` address fix is a strict improvement (typed object instead of raw dict).
Open in Web Open in Cursor 
--- unit/models/__init__.py | 28 ++++++++++-- unit/models/authorization.py | 82 +++++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 14 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index 88e69d0f..916e5892 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -517,6 +517,20 @@ def from_json_api(data: Dict): return CurrencyConversion(data["originalCurrency"], data["amountInOriginalCurrency"], data.get("fxRate")) +class CardVerificationData(UnitDTO): + def __init__(self, verification_method: Optional[str] = None): + self.verification_method = verification_method + + @staticmethod + def from_json_api(data: Dict): + if not data: + return None + + return CardVerificationData( + verification_method=data.get("verificationMethod"), + ) + + class RichMerchantDataFacilitator(object): def __init__(self, name: str, _type: Optional[str], logo: Optional[str]): self.name = name @@ -585,7 +599,13 @@ def from_json_api(data: Dict): if not data: return None - return RichMerchantData(data["name"], data.get("website"), data.get("logo"), data.get("phone"), - RichMerchantDataCategory.from_json_api(data.get("categories")), data.get("address"), - Coordinates.from_json_api(data.get("coordinates")), - RichMerchantDataFacilitator.from_json_api(data.get("facilitators"))) + return RichMerchantData( + name=data["name"], + website=data.get("website"), + logo=data.get("logo"), + phone=data.get("phone"), + categories=RichMerchantDataCategory.from_json_api(data.get("categories")), + address=RichMerchantDataAddress.from_json_api(data.get("address")), + coordinates=Coordinates.from_json_api(data.get("coordinates")), + facilitators=RichMerchantDataFacilitator.from_json_api(data.get("facilitators")), + ) diff --git a/unit/models/authorization.py b/unit/models/authorization.py index 9f6de45b..b32c88a0 100644 --- a/unit/models/authorization.py +++ b/unit/models/authorization.py @@ -5,23 +5,85 @@ AuthorizationStatus = Literal["Authorized", "Completed", "Canceled", "Declined"] + class AuthorizationDTO(object): - def __init__(self, id: str, created_at: datetime, amount: int, card_last_4_digits: str, status: AuthorizationStatus, - merchant: Merchant, recurring: bool, tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): + def __init__( + self, + id: str, + created_at: datetime, + amount: int, + card_last_4_digits: str, + status: AuthorizationStatus, + merchant: Merchant, + recurring: bool, + relationships: Optional[Dict[str, Relationship]] = None, + tags: Optional[Dict[str, str]] = None, + decline_reason: Optional[str] = None, + decline_description: Optional[str] = None, + declined_by: Optional[str] = None, + payment_method: Optional[str] = None, + digital_wallet: Optional[str] = None, + card_verification_data: Optional[CardVerificationData] = None, + card_network: Optional[str] = None, + cash_withdrawal_amount: Optional[int] = None, + summary: Optional[str] = None, + rich_merchant_data: Optional[RichMerchantData] = None, + currency_conversion: Optional[CurrencyConversion] = None, + ): self.id = id self.type = "authorization" - self.attributes = {"createdAt": created_at, "amount": amount, "cardLast4Digits": card_last_4_digits, - "status": status, "merchant": merchant, - "recurring": recurring, "tags": tags} + self.attributes = { + "createdAt": created_at, + "amount": amount, + "cardLast4Digits": card_last_4_digits, + "status": status, + "merchant": merchant, + "recurring": recurring, + "tags": tags, + "declineReason": decline_reason, + "declineDescription": decline_description, + "declinedBy": declined_by, + "paymentMethod": payment_method, + "digitalWallet": digital_wallet, + "cardVerificationData": card_verification_data, + "cardNetwork": card_network, + "cashWithdrawalAmount": cash_withdrawal_amount, + "summary": summary, + "richMerchantData": rich_merchant_data, + "currencyConversion": currency_conversion, + } self.relationships = relationships @staticmethod def from_json_api(_id, _type, attributes, relationships): - return AuthorizationDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["amount"], - attributes["cardLast4Digits"], attributes["status"], - Merchant.from_json_api(attributes["merchant"]), attributes["recurring"], - attributes.get("tags"), relationships) + return AuthorizationDTO( + id=_id, + created_at=date_utils.to_datetime(attributes["createdAt"]), + amount=attributes["amount"], + card_last_4_digits=attributes["cardLast4Digits"], + status=attributes["status"], + merchant=Merchant.from_json_api(attributes["merchant"]), + recurring=attributes["recurring"], + relationships=relationships, + tags=attributes.get("tags"), + decline_reason=attributes.get("declineReason"), + decline_description=attributes.get("declineDescription"), + declined_by=attributes.get("declinedBy"), + payment_method=attributes.get("paymentMethod"), + digital_wallet=attributes.get("digitalWallet"), + card_verification_data=CardVerificationData.from_json_api( + attributes.get("cardVerificationData") + ), + card_network=attributes.get("cardNetwork"), + cash_withdrawal_amount=attributes.get("cashWithdrawalAmount"), + summary=attributes.get("summary"), + rich_merchant_data=RichMerchantData.from_json_api( + attributes.get("richMerchantData") + ), + currency_conversion=CurrencyConversion.from_json_api( + attributes.get("currencyConversion") + ), + ) class ListAuthorizationParams(UnitParams):