Coverage for src/integrify/azericard/schemas/callback.py: 100%

43 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-12-29 00:57 +0000

1from decimal import Decimal 

2from hashlib import md5 

3from typing import Optional 

4 

5from pydantic import AliasGenerator, BaseModel, ConfigDict, Field, model_validator 

6from pydantic.alias_generators import to_pascal 

7from typing_extensions import Self 

8 

9from integrify.azericard import env 

10from integrify.azericard.schemas.common import AzeriCardMinimalWithAmountDataSchema 

11from integrify.azericard.schemas.enums import Action, CardStatus 

12from integrify.azericard.utils import TimeStampIn 

13 

14 

15class AuthCallbackSchema(AzeriCardMinimalWithAmountDataSchema): 

16 model_config = ConfigDict(alias_generator=AliasGenerator(validation_alias=str.upper)) 

17 

18 action: Action 

19 """EGateway fəaliyyət kodu""" 

20 

21 rc: str = Field(min_length=2, max_length=2) 

22 """Əməliyyat cavab kodu (ISO-8583 Sahə 39)""" 

23 

24 approval: Optional[str] = Field(..., min_length=6, max_length=6) 

25 """Müştəri bankının təsdiq kodu (ISO-8583 Sahə 38). Kart idarəetmə sistemi 

26 tərəfindən təmin edilmədikdə boş ola bilər.""" 

27 

28 rrn: str = Field(min_length=12, max_length=12) 

29 """Müştəri bankının axtarış istinad nömrəsi (ISO-8583 Sahə 37)""" 

30 

31 int_ref: str = Field(min_length=1, max_length=128) 

32 """Elektron ticarət şlüzünün daxili istinad nömrəsi""" 

33 

34 p_sign: str = Field(min_length=1, max_length=256) 

35 """16-lıq formatda Merchant MAC""" 

36 

37 

38class AuthCallbackWithCardDataSchema(AuthCallbackSchema): 

39 card: Optional[str] 

40 """Masklanmış kart nömrəsi""" 

41 

42 token: Optional[str] 

43 """Saxlanılacaq kartın TOKEN parametri""" 

44 

45 

46class TransferCallbackSchema(BaseModel): 

47 model_config = ConfigDict(alias_generator=AliasGenerator(validation_alias=to_pascal)) 

48 

49 operation_id: str = Field(min_length=16, max_length=20, validation_alias='OperationID') 

50 """AzeriCard tərəfindən verilmiş unikal əməliyyat nömrəsi""" 

51 

52 srn: str = Field(min_length=1, max_length=12, validation_alias='SRN') 

53 """Tərəfinizdən unikal əməliyyat nömrəsi""" 

54 

55 amount: Decimal 

56 """Müraciətdən gələn məbləğ""" 

57 

58 cur: str 

59 """Sorğunun valyutası yalnız AZN) olmalıdır""" 

60 

61 card_status: CardStatus 

62 """Azericard tərəfində istifadəçi kartı statusu""" 

63 

64 receiver_pan: str = Field(min_length=16, max_length=16, validation_alias='ReceiverPAN') 

65 """Maskalı kart nömrəsi""" 

66 

67 status: str 

68 """Cari tranzaksiya statusu (məsələn, "pending")""" 

69 

70 timestamp: TimeStampIn 

71 """Cavab vaxtı""" 

72 

73 rc: str = Field(validation_alias='Response Code') 

74 """Cavab kodu""" 

75 

76 message: str 

77 """Cavab mesajı""" 

78 

79 signature: str 

80 """Hesablanmış dəyər MD5(Bütün sahələr birləşdirilib + Açar)""" 

81 

82 @model_validator(mode='after') 

83 def validate_signature(self) -> Self: 

84 """AzeriCard-dan gələn signature-ni təsdiqləmə funksiyası""" 

85 with open(env.AZERICARD_KEY_FILE_PATH, encoding='utf-8') as key_file: 

86 key = key_file.read().strip().replace('\r\n', '\n') 

87 

88 calc_signature = md5( 

89 str( 

90 self.operation_id 

91 + self.srn 

92 + str(self.amount) 

93 + self.cur 

94 + self.card_status 

95 + self.receiver_pan 

96 + self.status 

97 + self.timestamp.strftime('%Y%m%d%H%M%S') 

98 + self.rc 

99 + self.message 

100 + key 

101 ).encode('utf-8'), 

102 usedforsecurity=False, 

103 ).hexdigest() 

104 

105 assert calc_signature == self.signature, 'Signature does not match!' 

106 

107 return self