from enum import StrEnum class AlipayErrorCode(StrEnum): SIGN_FAILED = "sign_failed" VERIFY_FAILED = "verify_failed" AUTH_FAILED = "auth_failed" RATE_LIMITED = "rate_limited" OUT_OF_WINDOW = "out_of_window" DAILY_LIMIT_EXCEEDED = "daily_limit_exceeded" USER_NOT_FOLLOWED = "user_not_followed" MSG_TOO_LONG = "msg_too_long" NETWORK_ERROR = "network_error" BUSINESS_ERROR = "business_error" UNKNOWN = "unknown" class AlipayError(Exception): def __init__( self, code: AlipayErrorCode, message: str = "", sub_code: str = "", sub_msg: str = "", retryable: bool = False, retry_after: float | None = None, ): self.code = code self.message = message self.sub_code = sub_code self.sub_msg = sub_msg self.retryable = retryable self.retry_after = retry_after super().__init__(f"[{code}] {message}" + (f" | {sub_code}: {sub_msg}" if sub_code else "")) _ERROR_CLASSIFICATION: dict[str, tuple[AlipayErrorCode, bool]] = { "40001": (AlipayErrorCode.SIGN_FAILED, False), "40002": (AlipayErrorCode.VERIFY_FAILED, False), "40004": (AlipayErrorCode.BUSINESS_ERROR, False), "40006": (AlipayErrorCode.AUTH_FAILED, False), "20000": (AlipayErrorCode.NETWORK_ERROR, True), "20001": (AlipayErrorCode.AUTH_FAILED, False), } _SUB_CODE_CLASSIFICATION: dict[str, tuple[AlipayErrorCode, bool]] = { "isv.invalid-signature": (AlipayErrorCode.SIGN_FAILED, False), "isv.invalid-app-id": (AlipayErrorCode.VERIFY_FAILED, False), "isv.message-count-per-day-isv-error": (AlipayErrorCode.DAILY_LIMIT_EXCEEDED, False), "isv.invalid-auth-token": (AlipayErrorCode.AUTH_FAILED, False), "isv.business-fail": (AlipayErrorCode.USER_NOT_FOLLOWED, False), } def classify_alipay_error(code: str, sub_code: str = "") -> tuple[AlipayErrorCode, bool]: if sub_code and sub_code in _SUB_CODE_CLASSIFICATION: return _SUB_CODE_CLASSIFICATION[sub_code] if code in _ERROR_CLASSIFICATION: return _ERROR_CLASSIFICATION[code] return (AlipayErrorCode.UNKNOWN, False) def is_retryable_error(code: str, sub_code: str = "") -> bool: _, retryable = classify_alipay_error(code, sub_code) return retryable