Pular para conteúdo

PaymentSDK

Source code in payment_emulation/payment/paymentSDK.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
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
129
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
class PaymentSDK():
    RESPONSE = {
        "transaction": None,
        "items": [],
        "redirect_urls": None,
        "address": None,
        "payer": None
    }

    TRANSACTION = ["success", "failure", "pending"]


    def __init__(
        self, 
        items: list[dict[str, str | int | float]], 
        redirect_urls: dict | None = None,
        address: dict | None = None, 
        payer: dict | None = None,
        **extra
        ):
        """Recebe um lista de produtos e outros dados adicionais para criar uma
        emulação de pagamento.

        Args:
            items: Uma lista contendo um ou mais dicionários de itens.
            redirect_urls: Um dicionário contendo URLs de redirecionamento para cada tipo de transação.
            address: Um dicionário contendo o endereço do pagador.
            payer: Um dicionário contendo os dados do pagador.
        """
        self._validate_items(items)
        self.items = items
        self.redirect_urls = redirect_urls
        self.address = address
        self.payer = payer
        self.extra = extra

        self._set_response()


    def card_credentials(
        self, card_number: str, validity: str, cvv: str, holder: str , 
        cpf: str | None = None, cnpj: str | None = None) -> Card | None:
        """Retornará um objeto `Card` se as credenciais forem 
        válidas, caso contrário, `None`.

        Args:
            cpf: CPF válido do titular. Deve ser informado se `cnpj` for None.
            cnpj: CNPJ válido do titular. Deve ser informado se `cpf` for None.
            card_number: Uma sequência de dígitos (máx.: 16).
            validity: Uma data com o mês e o ano. Ex: 12/24.
            cvv: Uma sequência de 3 dígitos.
            holder: O nome registrado do titular.

        Note:
            Os parâmetros `cpf` e `cnpj` devem ser passados sem formatação, ou seja, sem pontuação (ex.: '.', '-', '/').

        Raises:
            ValueError: Se ambos os parâmetros `cpf` e `cnpj` forem None ou se ambos forem informados.

        Returns:
            Um objeto `Card` ou `None`.
        """

        if (cpf is None) == (cnpj is None):
            raise ValueError(
                'Informe exatamente um dos parâmetros: cpf ou cnpj'
            )

        error_count = 0

        card = Card.objects.filter(card_number=card_number.strip()).first()
        if card is None: return
        if not card.active: return
        if card.account.status == 'BL': return

        if cpf:
            if card.account.cpf != cpf.strip(): 
                error_count += 1

        if cnpj:
            if card.account.cnpj !=cnpj.strip():
                error_count += 1

        if card.cvv != cvv.strip(): error_count += 1

        if validity.strip()[0] == '0':
            validity = validity.strip()[1:]

        if str(card.validity.year)[2:] != validity.strip().split('/')[1]: 
            error_count += 1

        if card.card_holder_name != holder.strip().upper(): error_count += 1

        if error_count == 0:
            return card
        else: return 


    def get_items_total_value(self) -> Decimal:
        """Recebe todos os itens, multiplica a `quantity` pelo `unit_price` e 
        retorna um valor em `Decimal`.

        Returns:
            Um valor `Decimal`.
        """
        value = 0
        for item in self.items:
            value += int(item["quantity"]) * float(item["unit_price"])

        decimal_value = Decimal(str(value))
        total_value = decimal_value.quantize(
                Decimal('0.01'),
                rounding=ROUND_HALF_UP
            )

        return total_value


    def _validate_items(self, items: list[dict]) -> None:
        """Verifica se os dicionários contidos na lista possuem as chaves 
        `quantity` e `unit_price`.

        Args:
            items: Uma lista passada no inicializador da classe.

        Raises:
            KeyError: Se as chaves obrigatórias `quantity` e `unit_price` não forem informadas.
        """
        missing_keys = []
        for item in items:
            if item.get('quantity') is None:
                missing_keys.append('quantity')

            if item.get('unit_price') is None:
                missing_keys.append('unit_price')

        if len(missing_keys) > 0:
            raise KeyError(
                f'The required {"key" if len(missing_keys) == 1 else "keys"}' 
                f' {", ".join(map(lambda x: f"`{x}`", missing_keys))} is'
                f' missing in the dict passed in the `items` parameter.'
            )


    def _set_response(self) -> None:
        """Define a resposta com os parâmetros passados no inicializador da classe.
        """
        self.RESPONSE["items"] = self.items
        self.RESPONSE["redirect_urls"] = self.redirect_urls
        self.RESPONSE["payer"] = self.payer
        self.RESPONSE["address"] = self.address
        if self.extra:
            for ex in self.extra.items():
                self.RESPONSE[ex[0]] = ex[1]


    def send_response(self, transaction: str) -> str:
        """Envia uma resposta em JSON com dados sobre a transação.

        Args:
            transaction: O resultado da transação (success, failure, pending).

        Raises:
            ValueError: Se a transação não for success, failure ou pending.

        Returns:
            Uma string em `JSON`.
        """
        if transaction not in self.TRANSACTION:
            raise ValueError(f'The `{transaction}` is not valid value.')

        self.RESPONSE["transaction"] = transaction
        if transaction == "success":
            self.RESPONSE["amount"] = float(self.get_items_total_value())
            self.RESPONSE["created_at"] = localtime(now()).isoformat()
        return json.dumps(self.RESPONSE)


    def payment(self, card_number: str, validity: str, cvv: str, holder: str,
                cpf: str | None = None, cnpj: str | None = None) -> str:
        """Recebe as credenciais do cartão e realiza uma transação.

        Args:
            cpf: CPF válido do titular. Deve ser informado se `cnpj` for None.
            cnpj: CNPJ válido do titular. Deve ser informado se `cpf` for None.
            card_number (str): Uma sequência de dígitos (máx.: 16).
            validity (str): Uma data com o mês e o ano. Ex: 12/24.
            cvv (str): Uma sequência de 3 dígitos.
            holder (str): O nome registrado do titular.

        Returns:
            Uma string em `JSON`.
        """
        card = self.card_credentials(
            cpf=cpf if cpf else None,
            cnpj=cnpj if cnpj else None,
            card_number=card_number, validity=validity, cvv=cvv, holder=holder
        )

        if card is None:
            return self.send_response(self.TRANSACTION[1])

        if card.card_flag == 'OTHER':
            return self.send_response(self.TRANSACTION[2])

        card_account_balance = Decimal(str(card.account.balance))
        items_total_value = self.get_items_total_value()

        if card_account_balance < items_total_value:
            return self.send_response(self.TRANSACTION[1])

        try:
            new_balane = card_account_balance - items_total_value
            card.account.balance = new_balane
            card.account.save()
        except: # pragma: no cover
            return self.send_response(self.TRANSACTION[1])

        return self.send_response(self.TRANSACTION[0])


    @staticmethod
    def set_seeds(instance: Card) -> dict:
        """Recebe uma instância do modelo `Card` e transforma os valores 
        em um dicionário.

        Args:
            instance: Uma instância do modelo `Card`.

        Returns:
            Um `dict` com os valores da instância.
        """
        month = instance.validity.month
        year = str(instance.validity.year)[2:]
        return {
                "account": {
                    "cpf": instance.account.cpf,
                    "account_holder_name": instance.account.account_holder_name,
                    "account_number": instance.account.account_number,
                    "balance": instance.account.balance,
                    "status": instance.account.get_status_display(),
                },
                "card": {
                    "card_holder_name": instance.card_holder_name,
                    "card_number": instance.card_number,
                    "validity": f'{month}/{year}',
                    "cvv": instance.cvv,
                    "card_flag": instance.card_flag,
                }
            }


    @classmethod
    def get_seeds(cls) -> dict:
        """Retorna as seeds caso elas estejam criadas. 

        Returns:
            Um `dict` com os valores das seeds.
        """
        SEEDS = {}
        if probatus := Card.objects.filter(card_holder_name='PROBATUS').first():
            SEEDS["PROBATUS"] = cls.set_seeds(probatus)

        if reprobi := Card.objects.filter(card_holder_name='REPROBI').first():
            SEEDS["REPROBI"] = cls.set_seeds(reprobi)

        if pendente := Card.objects.filter(card_holder_name='PENDENTE').first():
            SEEDS["PENDENTE"] = cls.set_seeds(pendente)

        return SEEDS

__init__(items, redirect_urls=None, address=None, payer=None, **extra)

Recebe um lista de produtos e outros dados adicionais para criar uma emulação de pagamento.

Parameters:

Name Type Description Default
items list[dict[str, str | int | float]]

Uma lista contendo um ou mais dicionários de itens.

required
redirect_urls dict | None

Um dicionário contendo URLs de redirecionamento para cada tipo de transação.

None
address dict | None

Um dicionário contendo o endereço do pagador.

None
payer dict | None

Um dicionário contendo os dados do pagador.

None
Source code in payment_emulation/payment/paymentSDK.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def __init__(
    self, 
    items: list[dict[str, str | int | float]], 
    redirect_urls: dict | None = None,
    address: dict | None = None, 
    payer: dict | None = None,
    **extra
    ):
    """Recebe um lista de produtos e outros dados adicionais para criar uma
    emulação de pagamento.

    Args:
        items: Uma lista contendo um ou mais dicionários de itens.
        redirect_urls: Um dicionário contendo URLs de redirecionamento para cada tipo de transação.
        address: Um dicionário contendo o endereço do pagador.
        payer: Um dicionário contendo os dados do pagador.
    """
    self._validate_items(items)
    self.items = items
    self.redirect_urls = redirect_urls
    self.address = address
    self.payer = payer
    self.extra = extra

    self._set_response()

_set_response()

Define a resposta com os parâmetros passados no inicializador da classe.

Source code in payment_emulation/payment/paymentSDK.py
151
152
153
154
155
156
157
158
159
160
def _set_response(self) -> None:
    """Define a resposta com os parâmetros passados no inicializador da classe.
    """
    self.RESPONSE["items"] = self.items
    self.RESPONSE["redirect_urls"] = self.redirect_urls
    self.RESPONSE["payer"] = self.payer
    self.RESPONSE["address"] = self.address
    if self.extra:
        for ex in self.extra.items():
            self.RESPONSE[ex[0]] = ex[1]

_validate_items(items)

Verifica se os dicionários contidos na lista possuem as chaves quantity e unit_price.

Parameters:

Name Type Description Default
items list[dict]

Uma lista passada no inicializador da classe.

required

Raises:

Type Description
KeyError

Se as chaves obrigatórias quantity e unit_price não forem informadas.

Source code in payment_emulation/payment/paymentSDK.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def _validate_items(self, items: list[dict]) -> None:
    """Verifica se os dicionários contidos na lista possuem as chaves 
    `quantity` e `unit_price`.

    Args:
        items: Uma lista passada no inicializador da classe.

    Raises:
        KeyError: Se as chaves obrigatórias `quantity` e `unit_price` não forem informadas.
    """
    missing_keys = []
    for item in items:
        if item.get('quantity') is None:
            missing_keys.append('quantity')

        if item.get('unit_price') is None:
            missing_keys.append('unit_price')

    if len(missing_keys) > 0:
        raise KeyError(
            f'The required {"key" if len(missing_keys) == 1 else "keys"}' 
            f' {", ".join(map(lambda x: f"`{x}`", missing_keys))} is'
            f' missing in the dict passed in the `items` parameter.'
        )

card_credentials(card_number, validity, cvv, holder, cpf=None, cnpj=None)

Retornará um objeto Card se as credenciais forem válidas, caso contrário, None.

Parameters:

Name Type Description Default
cpf str | None

CPF válido do titular. Deve ser informado se cnpj for None.

None
cnpj str | None

CNPJ válido do titular. Deve ser informado se cpf for None.

None
card_number str

Uma sequência de dígitos (máx.: 16).

required
validity str

Uma data com o mês e o ano. Ex: 12/24.

required
cvv str

Uma sequência de 3 dígitos.

required
holder str

O nome registrado do titular.

required
Note

Os parâmetros cpf e cnpj devem ser passados sem formatação, ou seja, sem pontuação (ex.: '.', '-', '/').

Raises:

Type Description
ValueError

Se ambos os parâmetros cpf e cnpj forem None ou se ambos forem informados.

Returns:

Type Description
Card | None

Um objeto Card ou None.

Source code in payment_emulation/payment/paymentSDK.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def card_credentials(
    self, card_number: str, validity: str, cvv: str, holder: str , 
    cpf: str | None = None, cnpj: str | None = None) -> Card | None:
    """Retornará um objeto `Card` se as credenciais forem 
    válidas, caso contrário, `None`.

    Args:
        cpf: CPF válido do titular. Deve ser informado se `cnpj` for None.
        cnpj: CNPJ válido do titular. Deve ser informado se `cpf` for None.
        card_number: Uma sequência de dígitos (máx.: 16).
        validity: Uma data com o mês e o ano. Ex: 12/24.
        cvv: Uma sequência de 3 dígitos.
        holder: O nome registrado do titular.

    Note:
        Os parâmetros `cpf` e `cnpj` devem ser passados sem formatação, ou seja, sem pontuação (ex.: '.', '-', '/').

    Raises:
        ValueError: Se ambos os parâmetros `cpf` e `cnpj` forem None ou se ambos forem informados.

    Returns:
        Um objeto `Card` ou `None`.
    """

    if (cpf is None) == (cnpj is None):
        raise ValueError(
            'Informe exatamente um dos parâmetros: cpf ou cnpj'
        )

    error_count = 0

    card = Card.objects.filter(card_number=card_number.strip()).first()
    if card is None: return
    if not card.active: return
    if card.account.status == 'BL': return

    if cpf:
        if card.account.cpf != cpf.strip(): 
            error_count += 1

    if cnpj:
        if card.account.cnpj !=cnpj.strip():
            error_count += 1

    if card.cvv != cvv.strip(): error_count += 1

    if validity.strip()[0] == '0':
        validity = validity.strip()[1:]

    if str(card.validity.year)[2:] != validity.strip().split('/')[1]: 
        error_count += 1

    if card.card_holder_name != holder.strip().upper(): error_count += 1

    if error_count == 0:
        return card
    else: return 

get_items_total_value()

Recebe todos os itens, multiplica a quantity pelo unit_price e retorna um valor em Decimal.

Returns:

Type Description
Decimal

Um valor Decimal.

Source code in payment_emulation/payment/paymentSDK.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def get_items_total_value(self) -> Decimal:
    """Recebe todos os itens, multiplica a `quantity` pelo `unit_price` e 
    retorna um valor em `Decimal`.

    Returns:
        Um valor `Decimal`.
    """
    value = 0
    for item in self.items:
        value += int(item["quantity"]) * float(item["unit_price"])

    decimal_value = Decimal(str(value))
    total_value = decimal_value.quantize(
            Decimal('0.01'),
            rounding=ROUND_HALF_UP
        )

    return total_value

get_seeds() classmethod

Retorna as seeds caso elas estejam criadas.

Returns:

Type Description
dict

Um dict com os valores das seeds.

Source code in payment_emulation/payment/paymentSDK.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
@classmethod
def get_seeds(cls) -> dict:
    """Retorna as seeds caso elas estejam criadas. 

    Returns:
        Um `dict` com os valores das seeds.
    """
    SEEDS = {}
    if probatus := Card.objects.filter(card_holder_name='PROBATUS').first():
        SEEDS["PROBATUS"] = cls.set_seeds(probatus)

    if reprobi := Card.objects.filter(card_holder_name='REPROBI').first():
        SEEDS["REPROBI"] = cls.set_seeds(reprobi)

    if pendente := Card.objects.filter(card_holder_name='PENDENTE').first():
        SEEDS["PENDENTE"] = cls.set_seeds(pendente)

    return SEEDS

payment(card_number, validity, cvv, holder, cpf=None, cnpj=None)

Recebe as credenciais do cartão e realiza uma transação.

Parameters:

Name Type Description Default
cpf str | None

CPF válido do titular. Deve ser informado se cnpj for None.

None
cnpj str | None

CNPJ válido do titular. Deve ser informado se cpf for None.

None
card_number str

Uma sequência de dígitos (máx.: 16).

required
validity str

Uma data com o mês e o ano. Ex: 12/24.

required
cvv str

Uma sequência de 3 dígitos.

required
holder str

O nome registrado do titular.

required

Returns:

Type Description
str

Uma string em JSON.

Source code in payment_emulation/payment/paymentSDK.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def payment(self, card_number: str, validity: str, cvv: str, holder: str,
            cpf: str | None = None, cnpj: str | None = None) -> str:
    """Recebe as credenciais do cartão e realiza uma transação.

    Args:
        cpf: CPF válido do titular. Deve ser informado se `cnpj` for None.
        cnpj: CNPJ válido do titular. Deve ser informado se `cpf` for None.
        card_number (str): Uma sequência de dígitos (máx.: 16).
        validity (str): Uma data com o mês e o ano. Ex: 12/24.
        cvv (str): Uma sequência de 3 dígitos.
        holder (str): O nome registrado do titular.

    Returns:
        Uma string em `JSON`.
    """
    card = self.card_credentials(
        cpf=cpf if cpf else None,
        cnpj=cnpj if cnpj else None,
        card_number=card_number, validity=validity, cvv=cvv, holder=holder
    )

    if card is None:
        return self.send_response(self.TRANSACTION[1])

    if card.card_flag == 'OTHER':
        return self.send_response(self.TRANSACTION[2])

    card_account_balance = Decimal(str(card.account.balance))
    items_total_value = self.get_items_total_value()

    if card_account_balance < items_total_value:
        return self.send_response(self.TRANSACTION[1])

    try:
        new_balane = card_account_balance - items_total_value
        card.account.balance = new_balane
        card.account.save()
    except: # pragma: no cover
        return self.send_response(self.TRANSACTION[1])

    return self.send_response(self.TRANSACTION[0])

send_response(transaction)

Envia uma resposta em JSON com dados sobre a transação.

Parameters:

Name Type Description Default
transaction str

O resultado da transação (success, failure, pending).

required

Raises:

Type Description
ValueError

Se a transação não for success, failure ou pending.

Returns:

Type Description
str

Uma string em JSON.

Source code in payment_emulation/payment/paymentSDK.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
def send_response(self, transaction: str) -> str:
    """Envia uma resposta em JSON com dados sobre a transação.

    Args:
        transaction: O resultado da transação (success, failure, pending).

    Raises:
        ValueError: Se a transação não for success, failure ou pending.

    Returns:
        Uma string em `JSON`.
    """
    if transaction not in self.TRANSACTION:
        raise ValueError(f'The `{transaction}` is not valid value.')

    self.RESPONSE["transaction"] = transaction
    if transaction == "success":
        self.RESPONSE["amount"] = float(self.get_items_total_value())
        self.RESPONSE["created_at"] = localtime(now()).isoformat()
    return json.dumps(self.RESPONSE)

set_seeds(instance) staticmethod

Recebe uma instância do modelo Card e transforma os valores em um dicionário.

Parameters:

Name Type Description Default
instance Card

Uma instância do modelo Card.

required

Returns:

Type Description
dict

Um dict com os valores da instância.

Source code in payment_emulation/payment/paymentSDK.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
@staticmethod
def set_seeds(instance: Card) -> dict:
    """Recebe uma instância do modelo `Card` e transforma os valores 
    em um dicionário.

    Args:
        instance: Uma instância do modelo `Card`.

    Returns:
        Um `dict` com os valores da instância.
    """
    month = instance.validity.month
    year = str(instance.validity.year)[2:]
    return {
            "account": {
                "cpf": instance.account.cpf,
                "account_holder_name": instance.account.account_holder_name,
                "account_number": instance.account.account_number,
                "balance": instance.account.balance,
                "status": instance.account.get_status_display(),
            },
            "card": {
                "card_holder_name": instance.card_holder_name,
                "card_number": instance.card_number,
                "validity": f'{month}/{year}',
                "cvv": instance.cvv,
                "card_flag": instance.card_flag,
            }
        }