fiscalapi 4.0.450__tar.gz → 4.0.462__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fiscalapi-4.0.450/fiscalapi.egg-info → fiscalapi-4.0.462}/PKG-INFO +7 -1
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/README.md +6 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/models/ticket_models.py +2 -2
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/base_service.py +3 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/invoice_service.py +11 -3
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/stamp_service.py +11 -3
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/ticket_service.py +6 -6
- {fiscalapi-4.0.450 → fiscalapi-4.0.462/fiscalapi.egg-info}/PKG-INFO +7 -1
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi.egg-info/SOURCES.txt +1 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/setup.py +1 -1
- fiscalapi-4.0.462/tests/test_idempotency_key.py +87 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/tests/test_ticket_service.py +6 -7
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/LICENSE.txt +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/MANIFEST.in +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/__init__.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/models/__init__.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/models/carta_porte_models.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/models/comercio_exterior_models.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/models/common_models.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/models/fiscalapi_models.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/models/manifest_models.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/models/sat_validation_models.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/models/webhook_models.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/py.typed +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/__init__.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/api_key_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/catalog_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/download_catalog_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/download_request_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/download_rule_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/employee_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/employer_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/fiscalapi_client.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/manifest_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/people_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/product_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/sat_validation_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/services/tax_file_service.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi/webhooks.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi.egg-info/dependency_links.txt +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi.egg-info/requires.txt +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/fiscalapi.egg-info/top_level.txt +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/pyproject.toml +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/setup.cfg +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/tests/test_cycle_contract.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/tests/test_error_trace_identifier.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/tests/test_invoice_item_ticket_id.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/tests/test_response_tolerance.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/tests/test_webhook_back_interop.py +0 -0
- {fiscalapi-4.0.450 → fiscalapi-4.0.462}/tests/test_webhook_verify.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fiscalapi
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.462
|
|
4
4
|
Summary: Genera facturas CFDI válidas ante el SAT consumiendo el API de https://www.fiscalapi.com
|
|
5
5
|
Home-page: https://www.fiscalapi.com
|
|
6
6
|
Author: Fiscalapi
|
|
@@ -284,6 +284,12 @@ invoice = Invoice(
|
|
|
284
284
|
api_response = client.invoices.create(invoice)
|
|
285
285
|
```
|
|
286
286
|
|
|
287
|
+
Para reintentar la creación sin duplicar la factura, pasa una `idempotency_key` propia (hasta 64 caracteres). El SDK la envía como header `Idempotency-Key`; repetir la misma llave de idempotencia con la misma factura en 24 h devuelve la respuesta original:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
api_response = client.invoices.create(invoice, idempotency_key="550e8400-e29b-41d4-a716-446655440000")
|
|
291
|
+
```
|
|
292
|
+
|
|
287
293
|
### 6. Crear la Misma Factura de Ingreso (Por Valores)
|
|
288
294
|
|
|
289
295
|
```python
|
|
@@ -247,6 +247,12 @@ invoice = Invoice(
|
|
|
247
247
|
api_response = client.invoices.create(invoice)
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
+
Para reintentar la creación sin duplicar la factura, pasa una `idempotency_key` propia (hasta 64 caracteres). El SDK la envía como header `Idempotency-Key`; repetir la misma llave de idempotencia con la misma factura en 24 h devuelve la respuesta original:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
api_response = client.invoices.create(invoice, idempotency_key="550e8400-e29b-41d4-a716-446655440000")
|
|
254
|
+
```
|
|
255
|
+
|
|
250
256
|
### 6. Crear la Misma Factura de Ingreso (Por Valores)
|
|
251
257
|
|
|
252
258
|
```python
|
|
@@ -132,8 +132,8 @@ class Ticket(BaseModel):
|
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
class TicketIngestionResult(BaseModel):
|
|
135
|
-
"""Respuesta de ``tickets.create``:
|
|
136
|
-
|
|
135
|
+
"""Respuesta de ``tickets.create``: un ticket nuevo (201) y la repetición con la misma llave de idempotencia y el mismo
|
|
136
|
+
cuerpo devuelven la misma respuesta original."""
|
|
137
137
|
|
|
138
138
|
id: Optional[str] = Field(default=None, alias="id", description="Id del ticket.")
|
|
139
139
|
ticket_code: Optional[str] = Field(default=None, alias="ticketCode", description="Código sin guiones, como lo lleva el QR; se imprime en grupos (XXXX-XXXX-XXXX).")
|
|
@@ -8,6 +8,9 @@ from fiscalapi.models.common_models import ApiResponse, FiscalApiSettings, Valid
|
|
|
8
8
|
|
|
9
9
|
T = TypeVar('T')
|
|
10
10
|
|
|
11
|
+
# Header que el integrador usa para reintentar una petición sin duplicarla (crear factura, timbres y tickets).
|
|
12
|
+
IDEMPOTENCY_KEY_HEADER = "Idempotency-Key"
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
class BaseService:
|
|
13
16
|
def __init__(self, settings: FiscalApiSettings):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
1
3
|
from fiscalapi.models.common_models import ApiResponse, PagedList
|
|
2
4
|
from fiscalapi.models.fiscalapi_models import (
|
|
3
5
|
CancelInvoiceRequest,
|
|
@@ -9,7 +11,7 @@ from fiscalapi.models.fiscalapi_models import (
|
|
|
9
11
|
InvoiceStatusResponse,
|
|
10
12
|
SendInvoiceRequest,
|
|
11
13
|
)
|
|
12
|
-
from fiscalapi.services.base_service import BaseService
|
|
14
|
+
from fiscalapi.services.base_service import IDEMPOTENCY_KEY_HEADER, BaseService
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class InvoiceService(BaseService):
|
|
@@ -25,7 +27,7 @@ class InvoiceService(BaseService):
|
|
|
25
27
|
endpoint = f"invoices/{invoice_id}"
|
|
26
28
|
return self.send_request("GET", endpoint, Invoice, details=details)
|
|
27
29
|
|
|
28
|
-
def create(self, invoice: Invoice) -> ApiResponse[Invoice]:
|
|
30
|
+
def create(self, invoice: Invoice, idempotency_key: Optional[str] = None) -> ApiResponse[Invoice]:
|
|
29
31
|
"""
|
|
30
32
|
Crea una nueva factura CFDI.
|
|
31
33
|
|
|
@@ -36,14 +38,20 @@ class InvoiceService(BaseService):
|
|
|
36
38
|
- 'N': Nómina
|
|
37
39
|
- 'T': Traslado
|
|
38
40
|
|
|
41
|
+
Con idempotency_key (hasta 64 caracteres ASCII visibles, sin espacios) se envía el header Idempotency-Key:
|
|
42
|
+
repetir la llave de idempotencia con la misma petición en 24 h devuelve la respuesta original; con otra petición
|
|
43
|
+
responde 422, y mientras la original sigue en proceso, 409. Sin llave de idempotencia el header no se envía.
|
|
44
|
+
|
|
39
45
|
Args:
|
|
40
46
|
invoice: Modelo de factura con los datos requeridos.
|
|
47
|
+
idempotency_key: Valor del header Idempotency-Key, o None para no enviarlo.
|
|
41
48
|
|
|
42
49
|
Returns:
|
|
43
50
|
ApiResponse con la factura creada y timbrada.
|
|
44
51
|
"""
|
|
45
52
|
endpoint = "invoices"
|
|
46
|
-
|
|
53
|
+
headers = {} if idempotency_key is None else {IDEMPOTENCY_KEY_HEADER: idempotency_key}
|
|
54
|
+
return self.send_request("POST", endpoint, Invoice, payload=invoice, headers=headers)
|
|
47
55
|
|
|
48
56
|
def cancel(self, cancel_invoice_request: CancelInvoiceRequest) -> ApiResponse[CancelInvoiceResponse]:
|
|
49
57
|
"""Cancela una factura."""
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Servicio para gestionar transacciones de timbres (stamps)."""
|
|
2
2
|
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
3
5
|
from fiscalapi.models import (
|
|
4
6
|
ApiResponse,
|
|
5
7
|
FiscalApiSettings,
|
|
@@ -7,7 +9,7 @@ from fiscalapi.models import (
|
|
|
7
9
|
StampTransaction,
|
|
8
10
|
StampTransactionParams,
|
|
9
11
|
)
|
|
10
|
-
from fiscalapi.services.base_service import BaseService
|
|
12
|
+
from fiscalapi.services.base_service import IDEMPOTENCY_KEY_HEADER, BaseService
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class StampService(BaseService):
|
|
@@ -41,20 +43,26 @@ class StampService(BaseService):
|
|
|
41
43
|
endpoint = f"stamps/{transaction_id}"
|
|
42
44
|
return self.send_request("GET", endpoint, StampTransaction)
|
|
43
45
|
|
|
44
|
-
def transfer_stamps(self, request: StampTransactionParams) -> ApiResponse[bool]:
|
|
46
|
+
def transfer_stamps(self, request: StampTransactionParams, idempotency_key: Optional[str] = None) -> ApiResponse[bool]:
|
|
45
47
|
"""Transfer stamps or validation credits from one person to another.
|
|
46
48
|
|
|
47
49
|
Use ``request.credit_type`` to pick which balance moves: ``CreditType.STAMP`` (default) moves
|
|
48
50
|
stamps, ``CreditType.VALIDATION`` moves SAT validation credits. The two balances never mix.
|
|
49
51
|
|
|
52
|
+
With ``idempotency_key`` (up to 64 visible ASCII characters, no spaces) the ``Idempotency-Key`` header is
|
|
53
|
+
sent: repeating the idempotency key with the same transfer within 24 hours answers the original response; with another
|
|
54
|
+
transfer, 422; while the original is still in progress, 409. Without it the header is not sent.
|
|
55
|
+
|
|
50
56
|
Args:
|
|
51
57
|
request: StampTransactionParams containing transfer details.
|
|
58
|
+
idempotency_key: Value of the Idempotency-Key header, or None to not send it.
|
|
52
59
|
|
|
53
60
|
Returns:
|
|
54
61
|
ApiResponse containing a boolean indicating success.
|
|
55
62
|
"""
|
|
56
63
|
endpoint = "stamps"
|
|
57
|
-
|
|
64
|
+
headers = {} if idempotency_key is None else {IDEMPOTENCY_KEY_HEADER: idempotency_key}
|
|
65
|
+
return self.send_request("POST", endpoint, bool, payload=request, headers=headers)
|
|
58
66
|
|
|
59
67
|
def withdraw_stamps(self, request: StampTransactionParams) -> ApiResponse[bool]:
|
|
60
68
|
"""Withdraw stamps or validation credits from a person.
|
|
@@ -5,9 +5,7 @@ from urllib.parse import urlencode
|
|
|
5
5
|
|
|
6
6
|
from fiscalapi.models.common_models import ApiResponse, PagedList
|
|
7
7
|
from fiscalapi.models.ticket_models import Ticket, TicketCodeResult, TicketIngestionResult, TicketListFilters
|
|
8
|
-
from fiscalapi.services.base_service import BaseService
|
|
9
|
-
|
|
10
|
-
IDEMPOTENCY_KEY_HEADER = "Idempotency-Key"
|
|
8
|
+
from fiscalapi.services.base_service import IDEMPOTENCY_KEY_HEADER, BaseService
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
class TicketService(BaseService):
|
|
@@ -16,9 +14,11 @@ class TicketService(BaseService):
|
|
|
16
14
|
def create(self, ticket: Ticket, idempotency_key: Optional[str] = None) -> ApiResponse[TicketIngestionResult]:
|
|
17
15
|
"""Ingest a ticket and charge 1 ticket credit. POST /api/v4/tickets
|
|
18
16
|
|
|
19
|
-
With ``idempotency_key`` (
|
|
20
|
-
the same
|
|
21
|
-
the
|
|
17
|
+
With ``idempotency_key`` (1 to 64 visible ASCII characters, no spaces) the ``Idempotency-Key`` header is sent:
|
|
18
|
+
repeating the same idempotency key with the same body, within 24 hours, returns the original response (201, with
|
|
19
|
+
the ``Idempotent-Replayed: true`` header) without registering another ticket or charging again; with another
|
|
20
|
+
body, 422; with the original still in progress, 409 with Retry-After. Without a key the header is not sent. An
|
|
21
|
+
active folio and series of the issuer answers 409.
|
|
22
22
|
|
|
23
23
|
Args:
|
|
24
24
|
ticket: Ticket to ingest.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fiscalapi
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.462
|
|
4
4
|
Summary: Genera facturas CFDI válidas ante el SAT consumiendo el API de https://www.fiscalapi.com
|
|
5
5
|
Home-page: https://www.fiscalapi.com
|
|
6
6
|
Author: Fiscalapi
|
|
@@ -284,6 +284,12 @@ invoice = Invoice(
|
|
|
284
284
|
api_response = client.invoices.create(invoice)
|
|
285
285
|
```
|
|
286
286
|
|
|
287
|
+
Para reintentar la creación sin duplicar la factura, pasa una `idempotency_key` propia (hasta 64 caracteres). El SDK la envía como header `Idempotency-Key`; repetir la misma llave de idempotencia con la misma factura en 24 h devuelve la respuesta original:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
api_response = client.invoices.create(invoice, idempotency_key="550e8400-e29b-41d4-a716-446655440000")
|
|
291
|
+
```
|
|
292
|
+
|
|
287
293
|
### 6. Crear la Misma Factura de Ingreso (Por Valores)
|
|
288
294
|
|
|
289
295
|
```python
|
|
@@ -40,6 +40,7 @@ fiscalapi/services/tax_file_service.py
|
|
|
40
40
|
fiscalapi/services/ticket_service.py
|
|
41
41
|
tests/test_cycle_contract.py
|
|
42
42
|
tests/test_error_trace_identifier.py
|
|
43
|
+
tests/test_idempotency_key.py
|
|
43
44
|
tests/test_invoice_item_ticket_id.py
|
|
44
45
|
tests/test_response_tolerance.py
|
|
45
46
|
tests/test_ticket_service.py
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Llave de idempotencia opcional en crear factura y transferir timbres.
|
|
2
|
+
|
|
3
|
+
Con ``idempotency_key`` el SDK manda el header ``Idempotency-Key`` con ese valor, y sin ella no manda el header. La llave de idempotencia
|
|
4
|
+
no cambia el cuerpo de la petición. El SDK no genera ni reintenta la llave de idempotencia: solo la pasa.
|
|
5
|
+
"""
|
|
6
|
+
from fiscalapi import Invoice, InvoiceIssuer, StampTransactionParams
|
|
7
|
+
|
|
8
|
+
INVOICES_URL = "https://sdk-tests.fiscalapi.invalid/api/v4/invoices"
|
|
9
|
+
STAMPS_URL = "https://sdk-tests.fiscalapi.invalid/api/v4/stamps"
|
|
10
|
+
ISSUER_ID = "4b1c2d3e-0000-4000-8000-000000000002"
|
|
11
|
+
PERSON_ID = "96b46762-d246-4a67-a562-510a25dbafa9"
|
|
12
|
+
IDEMPOTENCY_KEY = "pos-01-F-1001"
|
|
13
|
+
|
|
14
|
+
CREATED_INVOICE = {"succeeded": True, "message": "OK", "data": {"id": "7c1d2e3f-0000-4000-8000-0000000000b2"}}
|
|
15
|
+
TRANSFERRED = {"succeeded": True, "message": "OK", "data": True}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _invoice() -> Invoice:
|
|
19
|
+
return Invoice(version_code="4.0", series="F", type_code="I", issuer=InvoiceIssuer(id=ISSUER_ID))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _transfer() -> StampTransactionParams:
|
|
23
|
+
return StampTransactionParams(from_person_id=ISSUER_ID, to_person_id=PERSON_ID, amount=10)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Crear factura
|
|
27
|
+
|
|
28
|
+
def test_create_invoice_sends_the_idempotency_key_header(client, fake_api):
|
|
29
|
+
fake_api.respond_with(CREATED_INVOICE)
|
|
30
|
+
|
|
31
|
+
client.invoices.create(_invoice(), idempotency_key=IDEMPOTENCY_KEY)
|
|
32
|
+
|
|
33
|
+
[request] = fake_api.requests
|
|
34
|
+
assert request["method"] == "POST"
|
|
35
|
+
assert request["url"] == INVOICES_URL
|
|
36
|
+
assert request["headers"]["Idempotency-Key"] == IDEMPOTENCY_KEY
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_create_invoice_without_a_key_does_not_send_the_header(client, fake_api):
|
|
40
|
+
fake_api.respond_with(CREATED_INVOICE)
|
|
41
|
+
|
|
42
|
+
client.invoices.create(_invoice())
|
|
43
|
+
|
|
44
|
+
[request] = fake_api.requests
|
|
45
|
+
assert "Idempotency-Key" not in request["headers"]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_create_invoice_key_does_not_change_the_body(client, fake_api):
|
|
49
|
+
fake_api.respond_with(CREATED_INVOICE)
|
|
50
|
+
|
|
51
|
+
client.invoices.create(_invoice())
|
|
52
|
+
client.invoices.create(_invoice(), idempotency_key=IDEMPOTENCY_KEY)
|
|
53
|
+
|
|
54
|
+
without_key, with_key = fake_api.requests
|
|
55
|
+
assert with_key["json"] == without_key["json"]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Transferir timbres
|
|
59
|
+
|
|
60
|
+
def test_transfer_stamps_sends_the_idempotency_key_header(client, fake_api):
|
|
61
|
+
fake_api.respond_with(TRANSFERRED)
|
|
62
|
+
|
|
63
|
+
client.stamps.transfer_stamps(_transfer(), idempotency_key=IDEMPOTENCY_KEY)
|
|
64
|
+
|
|
65
|
+
[request] = fake_api.requests
|
|
66
|
+
assert request["method"] == "POST"
|
|
67
|
+
assert request["url"] == STAMPS_URL
|
|
68
|
+
assert request["headers"]["Idempotency-Key"] == IDEMPOTENCY_KEY
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_transfer_stamps_without_a_key_does_not_send_the_header(client, fake_api):
|
|
72
|
+
fake_api.respond_with(TRANSFERRED)
|
|
73
|
+
|
|
74
|
+
client.stamps.transfer_stamps(_transfer())
|
|
75
|
+
|
|
76
|
+
[request] = fake_api.requests
|
|
77
|
+
assert "Idempotency-Key" not in request["headers"]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_transfer_stamps_key_does_not_change_the_body(client, fake_api):
|
|
81
|
+
fake_api.respond_with(TRANSFERRED)
|
|
82
|
+
|
|
83
|
+
client.stamps.transfer_stamps(_transfer())
|
|
84
|
+
client.stamps.transfer_stamps(_transfer(), idempotency_key=IDEMPOTENCY_KEY)
|
|
85
|
+
|
|
86
|
+
without_key, with_key = fake_api.requests
|
|
87
|
+
assert with_key["json"] == without_key["json"]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Servicio de tickets de Auto CFDI (POST/GET/DELETE /api/v4/tickets).
|
|
2
2
|
|
|
3
3
|
Los mismos escenarios viven en los SDK de .NET, Node.js, PHP y Java: el cuerpo exacto de la ingesta y el header
|
|
4
|
-
Idempotency-Key solo si se pasa;
|
|
4
|
+
Idempotency-Key solo si se pasa; la respuesta 201 de la ingesta; el 409 y el 422 con la forma de error del SDK
|
|
5
5
|
(``details`` con el texto del API y ``data`` en None); la query de la lista con ``status`` repetido; el detalle con y sin
|
|
6
6
|
``details``; el código; la cancelación con DELETE, y un estado que el SDK no conoce, conservado como texto.
|
|
7
7
|
"""
|
|
@@ -26,7 +26,7 @@ BASE_URL = "https://sdk-tests.fiscalapi.invalid/api/v4/tickets"
|
|
|
26
26
|
ISSUER_ID = "4b1c2d3e-0000-4000-8000-000000000002"
|
|
27
27
|
TICKET_ID = "9f8e7d6c-0000-4000-8000-0000000000a1"
|
|
28
28
|
GLOBAL_INVOICE_ID = "7c1d2e3f-0000-4000-8000-0000000000b2"
|
|
29
|
-
IDEMPOTENCY_KEY = "
|
|
29
|
+
IDEMPOTENCY_KEY = "550e8400-e29b-41d4-a716-446655440000"
|
|
30
30
|
|
|
31
31
|
INGESTED = {
|
|
32
32
|
"id": TICKET_ID,
|
|
@@ -158,16 +158,15 @@ def test_create_without_a_key_does_not_send_the_idempotency_key_header(client, f
|
|
|
158
158
|
assert "Idempotency-Key" not in request["headers"]
|
|
159
159
|
|
|
160
160
|
|
|
161
|
-
# b.
|
|
161
|
+
# b. Respuesta de la ingesta: la repetición con la misma llave de idempotencia devuelve esta misma 201
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
fake_api.respond_with(_ok(INGESTED, status_code), status_code=status_code)
|
|
163
|
+
def test_create_reads_the_new_ticket_with_its_shape(client, fake_api):
|
|
164
|
+
fake_api.respond_with(_ok(INGESTED, 201), status_code=201)
|
|
166
165
|
|
|
167
166
|
response = client.tickets.create(_new_ticket(), IDEMPOTENCY_KEY)
|
|
168
167
|
|
|
169
168
|
assert response.succeeded is True
|
|
170
|
-
assert response.http_status_code ==
|
|
169
|
+
assert response.http_status_code == 201
|
|
171
170
|
assert response.error_data is None
|
|
172
171
|
ingested = response.data
|
|
173
172
|
assert ingested.id == TICKET_ID
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|