Passed
Push — main ( 44031f...fd93eb )
by Sat CFDI
05:07
created

SATFacturaElectronica.legal_name_valid()   A

Complexity

Conditions 2

Size

Total Lines 11
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 4.048

Importance

Changes 0
Metric Value
cc 2
eloc 10
nop 3
dl 0
loc 11
ccs 1
cts 5
cp 0.2
crap 4.048
rs 9.9
c 0
b 0
f 0
1 1
import json
2 1
import pickle
3 1
from time import time
4
5 1
import requests
6 1
import urllib3
7 1
from requests.structures import CaseInsensitiveDict
8
9 1
from .utils import get_form, generate_token, request_ref_headers, request_verification_token, random_ajax_id
10 1
from .. import Signer, ResponseError
11
12
13 1
class PortalManager(requests.Session):
14 1
    def __init__(self, signer: Signer):
15 1
        super().__init__()
16 1
        try:
17 1
            urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH'
18 1
        except:
19 1
            pass
20 1
        self.signer = signer
21
22 1
        self.headers = CaseInsensitiveDict(
23
            {
24
                "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
25
                "Accept-Encoding": 'gzip, deflate, br',
26
                "Accept": 'text/html,application/xhtml+xml,application/xml',
27
                "Connection": "keep-alive",
28
                'Pragma': 'no-cache',
29
                'Cache-Control': 'no-cache',
30
            }
31
        )
32
33 1
    def save_session(self, target):
34 1
        pickle.dump(self.cookies, target)
35
36 1
    def load_session(self, source):
37
        self.cookies.update(pickle.load(source))
38
39 1
    def form(self, action, referer_url, data):
40
        res = self.post(
41
            url=action,
42
            headers=request_ref_headers(referer_url),
43
            data=data
44
        )
45
        assert res.status_code == 200
46
        return res
47
48 1
    def fiel_login(self, login_response):
49
        action, data = get_form(login_response, id='certform')
50
        return self.form(
51
            action,
52
            login_response.request.url,
53
            data | {
54
                'token': generate_token(self.signer, code=data['guid']),
55
                'fert': self.signer.certificate.get_notAfter()[2:].decode(),
56
            }
57
        )
58
59
60 1
class SATPortal(PortalManager):
61 1
    BASE_URL = 'https://loginda.siat.sat.gob.mx'
62
63 1
    def login(self):
64
        res = self.get(
65
            url=f'{self.BASE_URL}/nidp/app/login?id=fiel'
66
        )
67
        assert res.status_code == 200
68
        action, data = get_form(res)
69
70
        return self.fiel_login(
71
            login_response=self.form(action, res.request.url, data)
72
        )
73
74 1
    def home_page(self):
75
        return self.get(
76
            url=f'{self.BASE_URL}/nidp/app?sid=0'
77
        )
78
79 1
    def logout(self):
80
        return self.get(
81
            url=f'{self.BASE_URL}/nidp/app/logout',
82
            headers={
83
                'referer': f'{self.BASE_URL}/nidp/app?sid=0'
84
            },
85
            allow_redirects=False
86
        )
87
88 1
    def declaraciones_provisionales_login(self):
89
        res = self.get(
90
            url='https://ptscdecprov.clouda.sat.gob.mx',
91
            allow_redirects=True
92
        )
93
        assert res.status_code == 200
94
95
        action, data = get_form(res)
96
        res = self.form(action, res.request.url, data)
97
        return res
98
99
100 1
class SATFacturaElectronica(PortalManager):
101 1
    BASE_URL = 'https://portal.facturaelectronica.sat.gob.mx'
102 1
    REQUEST_CONTEXT = 'appId=cid-v1:20ff76f4-0bca-495f-b7fd-09ca520e39f7'
103
104 1
    def __init__(self, signer: Signer):
105
        super().__init__(signer)
106
        self._ajax_id = random_ajax_id()
107
        self._request_verification_token = None
108
109 1
    def login(self):
110
        res = self.get(
111
            url=self.BASE_URL
112
        )
113
        assert res.status_code == 200
114
        try:
115
            action, data = get_form(res)
116
        except IndexError as ex:
117
            raise ValueError("Login form not found, please try again") from ex
118
119
        if action.startswith('https://cfdiau.sat.gob.mx/'):
120
            assert 'nidp/wsfed/ep?id=SATUPCFDiCon' in action
121
122
            res = self.fiel_login(
123
                login_response=self.form(
124
                    action.replace('nidp/wsfed/ep?id=SATUPCFDiCon', 'nidp/app/login?id=SATx509Custom'),
125
                    res.request.url,
126
                    data
127
                )
128
            )
129
130
            action, data = get_form(res)
131
            res = self.form(action, res.request.url, data)
132
133
            action, data = get_form(res)
134
135
        res = self.form(action, res.request.url, data)
136
137
        self._request_verification_token = request_verification_token(res)
138
        self._ajax_id = random_ajax_id()
139
        return res
140
141 1
    def _reload_verification_token(self):
142
        res = self.get(
143
            url=f'{self.BASE_URL}/Factura/GeneraFactura',
144
            allow_redirects=False
145
        )
146
        if res.status_code == 200:
147
            self._request_verification_token = request_verification_token(res)
148
        else:
149
            raise ValueError('Please Login Again')
150
151 1
    def reactivate_session(self):
152
        res = self.post(
153
            url=f'{self.BASE_URL}/Home/ReActiveSession',
154
            headers={
155
                'Origin': self.BASE_URL,
156
                'Request-Context': self.REQUEST_CONTEXT,
157
                'Request-Id': f'|{self._ajax_id}.{random_ajax_id()}'
158
            },
159
            allow_redirects=False
160
        )
161
        return res
162
163 1
    def _request(self, method, path, data=None, params=None):
164
        if self._request_verification_token is None:
165
            self._reload_verification_token()
166
167
        res = self.request(
168
            method=method,
169
            url=f'{self.BASE_URL}/{path}',
170
            headers={
171
                'Origin': self.BASE_URL,
172
                'Authority': self.BASE_URL,
173
                'Request-Context': self.REQUEST_CONTEXT,
174
                '__RequestVerificationToken': self._request_verification_token,
175
                'Request-Id': f'|{self._ajax_id}.{random_ajax_id()}'  # |pR4Px.o0yAS
176
            },
177
            data=data,
178
            params=params,
179
            allow_redirects=False
180
        )
181
        if res.status_code == 200:
182
            return res.json()
183
        else:
184
            raise ResponseError(res)
185
186 1
    def legal_name_valid(self, rfc, legal_name):
187
        res = self._request(
188
            method='POST',
189
            path='Clientes/ValidaRazonSocialRFC',
190
            data={
191
                'rfcValidar': rfc.upper(),
192
                'razonSocial': legal_name.upper(),
193
            })
194
        if not res['exitoso']:
195
            raise ResponseError(res)
196
        return res['resultado']
197
198 1
    def rfc_valid(self, rfc):
199
        res = self._request(
200
            method='POST',
201
            path='Clientes/ExisteLrfc',
202
            data={
203
                'rfcValidar': rfc.upper()
204
            }
205
        )
206
        if not res['exitoso']:
207
            raise ResponseError(res)
208
        return res['resultado']
209
210 1
    def lco_details(self, rfc, apply_border_region=True):
211
        res = self._request(
212
            method='GET',
213
            path='Clientes/ValidaLco',
214
            params={
215
                'rfcValidar': rfc.upper(),
216
                'aplicaRegionFronteriza': apply_border_region,
217
                "_": int(time() * 1000)
218
            }
219
        )
220
        return json.loads(res)
221