跳到内容

支付

pydantic_extra_types.payment 模块提供了 PaymentCardNumber 数据类型。

PaymentCardBrand

Bases: str, Enum

PaymentCardNumber 支持的支付卡品牌。

PaymentCardNumber

PaymentCardNumber(card_number: str)

Bases: str

一种 支付卡号码

源代码位于 .venv/lib/python3.12/site-packages/pydantic_extra_types/payment.py
51
52
53
54
55
56
57
58
def __init__(self, card_number: str):
    self.validate_digits(card_number)

    card_number = self.validate_luhn_check_digit(card_number)

    self.bin = card_number[:6]
    self.last4 = card_number[-4:]
    self.brand = self.validate_brand(card_number)

strip_whitespace class-attribute

strip_whitespace: bool = True

是否从输入值中去除空格。

min_length class-attribute

min_length: int = 12

卡号的最小长度。

max_length class-attribute

max_length: int = 19

卡号的最大长度。

bin instance-attribute

bin: str = card_number[:6]

卡号的前 6 位数字。

last4 instance-attribute

last4: str = card_number[-4:]

卡号的后 4 位数字。

brand instance-attribute

brand: PaymentCardBrand = validate_brand(card_number)

卡的品牌。

masked property

masked: str

掩码后的卡号。

validate classmethod

validate(
    __input_value: str, _: ValidationInfo
) -> PaymentCardNumber

验证 PaymentCardNumber 实例。

参数

名称 类型 描述 默认值
__input_value str

要验证的输入值。

required
_ ValidationInfo

验证信息。

required

返回值

类型 描述
PaymentCardNumber

验证后的 PaymentCardNumber 实例。

源代码位于 .venv/lib/python3.12/site-packages/pydantic_extra_types/payment.py
69
70
71
72
73
74
75
76
77
78
79
80
@classmethod
def validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> PaymentCardNumber:
    """Validate the `PaymentCardNumber` instance.

    Args:
        __input_value: The input value to validate.
        _: The validation info.

    Returns:
        The validated `PaymentCardNumber` instance.
    """
    return cls(__input_value)

validate_digits classmethod

validate_digits(card_number: str) -> None

验证卡号是否全部为数字。

参数

名称 类型 描述 默认值
card_number str

要验证的卡号。

required

抛出

类型 描述
PydanticCustomError

如果卡号不是全部为数字。

源代码位于 .venv/lib/python3.12/site-packages/pydantic_extra_types/payment.py
88
89
90
91
92
93
94
95
96
97
98
99
@classmethod
def validate_digits(cls, card_number: str) -> None:
    """Validate that the card number is all digits.

    Args:
        card_number: The card number to validate.

    Raises:
        PydanticCustomError: If the card number is not all digits.
    """
    if not card_number or not all('0' <= c <= '9' for c in card_number):
        raise PydanticCustomError('payment_card_number_digits', 'Card number is not all digits')

validate_luhn_check_digit classmethod

validate_luhn_check_digit(card_number: str) -> str

验证支付卡号码。基于 Luhn 算法

参数

名称 类型 描述 默认值
card_number str

要验证的卡号。

required

返回值

类型 描述
str

验证后的卡号。

抛出

类型 描述
PydanticCustomError

如果卡号无效。

源代码位于 .venv/lib/python3.12/site-packages/pydantic_extra_types/payment.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
@classmethod
def validate_luhn_check_digit(cls, card_number: str) -> str:
    """Validate the payment card number.
    Based on the [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm).

    Args:
        card_number: The card number to validate.

    Returns:
        The validated card number.

    Raises:
        PydanticCustomError: If the card number is not valid.
    """
    sum_ = int(card_number[-1])
    length = len(card_number)
    parity = length % 2
    for i in range(length - 1):
        digit = int(card_number[i])
        if i % 2 == parity:
            digit *= 2
        if digit > 9:
            digit -= 9
        sum_ += digit
    valid = sum_ % 10 == 0
    if not valid:
        raise PydanticCustomError('payment_card_number_luhn', 'Card number is not luhn valid')
    return card_number

validate_brand staticmethod

validate_brand(card_number: str) -> PaymentCardBrand

基于主要品牌的 BIN 验证长度。

参数

名称 类型 描述 默认值
card_number str

要验证的卡号。

required

返回值

类型 描述
PaymentCardBrand

验证后的卡品牌。

抛出

类型 描述
PydanticCustomError

如果卡号无效。

源代码位于 .venv/lib/python3.12/site-packages/pydantic_extra_types/payment.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
@staticmethod
def validate_brand(card_number: str) -> PaymentCardBrand:
    """Validate length based on
    [BIN](https://en.wikipedia.org/wiki/Payment_card_number#Issuer_identification_number_(IIN))
    for major brands.

    Args:
        card_number: The card number to validate.

    Returns:
        The validated card brand.

    Raises:
        PydanticCustomError: If the card number is not valid.
    """
    brand = PaymentCardBrand.other

    if card_number[0] == '4':
        brand = PaymentCardBrand.visa
        required_length = [13, 16, 19]
    elif 51 <= int(card_number[:2]) <= 55:
        brand = PaymentCardBrand.mastercard
        required_length = [16]
    elif card_number[:2] in {'34', '37'}:
        brand = PaymentCardBrand.amex
        required_length = [15]
    elif 2200 <= int(card_number[:4]) <= 2204:
        brand = PaymentCardBrand.mir
        required_length = list(range(16, 20))
    elif card_number[:4] in {'5018', '5020', '5038', '5893', '6304', '6759', '6761', '6762', '6763'} or card_number[
        :6
    ] in (
        '676770',
        '676774',
    ):
        brand = PaymentCardBrand.maestro
        required_length = list(range(12, 20))
    elif card_number.startswith('65') or 644 <= int(card_number[:3]) <= 649 or card_number.startswith('6011'):
        brand = PaymentCardBrand.discover
        required_length = list(range(16, 20))
    elif (
        506099 <= int(card_number[:6]) <= 506198
        or 650002 <= int(card_number[:6]) <= 650027
        or 507865 <= int(card_number[:6]) <= 507964
    ):
        brand = PaymentCardBrand.verve
        required_length = [16, 18, 19]
    elif card_number[:4] in {'5019', '4571'}:
        brand = PaymentCardBrand.dankort
        required_length = [16]
    elif card_number.startswith('9792'):
        brand = PaymentCardBrand.troy
        required_length = [16]
    elif card_number[:2] in {'62', '81'}:
        brand = PaymentCardBrand.unionpay
        required_length = [16, 19]
    elif 3528 <= int(card_number[:4]) <= 3589:
        brand = PaymentCardBrand.jcb
        required_length = [16, 19]

    valid = len(card_number) in required_length if brand != PaymentCardBrand.other else True

    if not valid:
        raise PydanticCustomError(
            'payment_card_number_brand',
            f'Length for a {brand} card must be {" or ".join(map(str, required_length))}',
            {'brand': brand, 'required_length': required_length},
        )

    return brand