django-checkouts 1.0.1__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.
Files changed (118) hide show
  1. django_checkouts-1.0.1/.github/workflows/release.yml +73 -0
  2. django_checkouts-1.0.1/.gitignore +14 -0
  3. django_checkouts-1.0.1/.python-version +1 -0
  4. django_checkouts-1.0.1/.release-please-manifest.json +1 -0
  5. django_checkouts-1.0.1/.superpowers/sdd/task-1-report.md +148 -0
  6. django_checkouts-1.0.1/.superpowers/sdd/task-2-report.md +68 -0
  7. django_checkouts-1.0.1/.superpowers/sdd/task-8-report.md +99 -0
  8. django_checkouts-1.0.1/CHANGELOG.md +50 -0
  9. django_checkouts-1.0.1/CLAUDE.md +133 -0
  10. django_checkouts-1.0.1/LICENSE +28 -0
  11. django_checkouts-1.0.1/PKG-INFO +146 -0
  12. django_checkouts-1.0.1/README.rst +115 -0
  13. django_checkouts-1.0.1/django_checkouts/__init__.py +9 -0
  14. django_checkouts-1.0.1/django_checkouts/apps.py +12 -0
  15. django_checkouts-1.0.1/django_checkouts/capabilities.py +107 -0
  16. django_checkouts-1.0.1/django_checkouts/checks.py +62 -0
  17. django_checkouts-1.0.1/django_checkouts/client.py +30 -0
  18. django_checkouts-1.0.1/django_checkouts/credentials.py +73 -0
  19. django_checkouts-1.0.1/django_checkouts/enums.py +199 -0
  20. django_checkouts-1.0.1/django_checkouts/exceptions.py +147 -0
  21. django_checkouts-1.0.1/django_checkouts/gateways/__init__.py +7 -0
  22. django_checkouts-1.0.1/django_checkouts/gateways/base.py +84 -0
  23. django_checkouts-1.0.1/django_checkouts/gateways/commands.py +126 -0
  24. django_checkouts-1.0.1/django_checkouts/gateways/contracts.py +47 -0
  25. django_checkouts-1.0.1/django_checkouts/gateways/options.py +15 -0
  26. django_checkouts-1.0.1/django_checkouts/gateways/stripe/__init__.py +8 -0
  27. django_checkouts-1.0.1/django_checkouts/gateways/stripe/capabilities.py +48 -0
  28. django_checkouts-1.0.1/django_checkouts/gateways/stripe/gateway.py +235 -0
  29. django_checkouts-1.0.1/django_checkouts/gateways/stripe/handlers/__init__.py +49 -0
  30. django_checkouts-1.0.1/django_checkouts/gateways/stripe/handlers/checkouts.py +247 -0
  31. django_checkouts-1.0.1/django_checkouts/gateways/stripe/handlers/events.py +77 -0
  32. django_checkouts-1.0.1/django_checkouts/gateways/stripe/handlers/invoices.py +40 -0
  33. django_checkouts-1.0.1/django_checkouts/gateways/stripe/handlers/subscriptions.py +232 -0
  34. django_checkouts-1.0.1/django_checkouts/gateways/stripe/mapping.py +1120 -0
  35. django_checkouts-1.0.1/django_checkouts/gateways/stripe/options.py +21 -0
  36. django_checkouts-1.0.1/django_checkouts/gateways/stripe/webhooks.py +76 -0
  37. django_checkouts-1.0.1/django_checkouts/integrations/__init__.py +3 -0
  38. django_checkouts-1.0.1/django_checkouts/integrations/drf.py +42 -0
  39. django_checkouts-1.0.1/django_checkouts/py.typed +0 -0
  40. django_checkouts-1.0.1/django_checkouts/registry.py +91 -0
  41. django_checkouts-1.0.1/django_checkouts/resources/__init__.py +19 -0
  42. django_checkouts-1.0.1/django_checkouts/resources/_validation.py +11 -0
  43. django_checkouts-1.0.1/django_checkouts/resources/checkouts.py +42 -0
  44. django_checkouts-1.0.1/django_checkouts/resources/events.py +37 -0
  45. django_checkouts-1.0.1/django_checkouts/resources/invoices.py +21 -0
  46. django_checkouts-1.0.1/django_checkouts/resources/setups.py +35 -0
  47. django_checkouts-1.0.1/django_checkouts/resources/subscriptions.py +68 -0
  48. django_checkouts-1.0.1/django_checkouts/resources/webhooks.py +23 -0
  49. django_checkouts-1.0.1/django_checkouts/testing/__init__.py +8 -0
  50. django_checkouts-1.0.1/django_checkouts/testing/contracts.py +212 -0
  51. django_checkouts-1.0.1/django_checkouts/testing/fakes.py +40 -0
  52. django_checkouts-1.0.1/django_checkouts/types/__init__.py +51 -0
  53. django_checkouts-1.0.1/django_checkouts/types/checkouts.py +103 -0
  54. django_checkouts-1.0.1/django_checkouts/types/common.py +107 -0
  55. django_checkouts-1.0.1/django_checkouts/types/events.py +68 -0
  56. django_checkouts-1.0.1/django_checkouts/types/invoices.py +93 -0
  57. django_checkouts-1.0.1/django_checkouts/types/setups.py +98 -0
  58. django_checkouts-1.0.1/django_checkouts/types/subscriptions.py +159 -0
  59. django_checkouts-1.0.1/django_checkouts/version.py +24 -0
  60. django_checkouts-1.0.1/django_checkouts/webhooks.py +173 -0
  61. django_checkouts-1.0.1/django_checkouts.egg-info/PKG-INFO +146 -0
  62. django_checkouts-1.0.1/django_checkouts.egg-info/SOURCES.txt +116 -0
  63. django_checkouts-1.0.1/django_checkouts.egg-info/dependency_links.txt +1 -0
  64. django_checkouts-1.0.1/django_checkouts.egg-info/requires.txt +5 -0
  65. django_checkouts-1.0.1/django_checkouts.egg-info/scm_file_list.json +112 -0
  66. django_checkouts-1.0.1/django_checkouts.egg-info/scm_version.json +8 -0
  67. django_checkouts-1.0.1/django_checkouts.egg-info/top_level.txt +1 -0
  68. django_checkouts-1.0.1/docs/__init__.py +3 -0
  69. django_checkouts-1.0.1/docs/conf.py +9 -0
  70. django_checkouts-1.0.1/docs/errors-and-retries.rst +48 -0
  71. django_checkouts-1.0.1/docs/gateway-options.rst +34 -0
  72. django_checkouts-1.0.1/docs/index.rst +25 -0
  73. django_checkouts-1.0.1/docs/integracao-sandbox-asaas.md +186 -0
  74. django_checkouts-1.0.1/docs/integracao-sandbox-pagseguro.md +164 -0
  75. django_checkouts-1.0.1/docs/integracao-sandbox-stripe.md +166 -0
  76. django_checkouts-1.0.1/docs/migration-pre-1.0.rst +60 -0
  77. django_checkouts-1.0.1/docs/plano-testes-sandbox.md +157 -0
  78. django_checkouts-1.0.1/docs/quickstart.rst +69 -0
  79. django_checkouts-1.0.1/docs/reconciliation.rst +35 -0
  80. django_checkouts-1.0.1/docs/subscriptions.rst +62 -0
  81. django_checkouts-1.0.1/docs/superpowers/plans/2026-08-07-recurring-checkout-lifecycle.md +883 -0
  82. django_checkouts-1.0.1/docs/superpowers/specs/2026-08-06-django-checkouts-recurring-lifecycle-design.md +825 -0
  83. django_checkouts-1.0.1/docs/webhooks.rst +32 -0
  84. django_checkouts-1.0.1/docs/writing-a-gateway.rst +40 -0
  85. django_checkouts-1.0.1/pyproject.toml +133 -0
  86. django_checkouts-1.0.1/release-please-config.json +11 -0
  87. django_checkouts-1.0.1/setup.cfg +4 -0
  88. django_checkouts-1.0.1/test_settings.py +38 -0
  89. django_checkouts-1.0.1/tests/__init__.py +1 -0
  90. django_checkouts-1.0.1/tests/conftest.py +35 -0
  91. django_checkouts-1.0.1/tests/gateways/__init__.py +1 -0
  92. django_checkouts-1.0.1/tests/gateways/stripe/__init__.py +1 -0
  93. django_checkouts-1.0.1/tests/gateways/stripe/fixtures/event_invoice_paid.json +49 -0
  94. django_checkouts-1.0.1/tests/gateways/stripe/fixtures/event_unknown.json +16 -0
  95. django_checkouts-1.0.1/tests/gateways/stripe/fixtures/invoice_failed.json +45 -0
  96. django_checkouts-1.0.1/tests/gateways/stripe/fixtures/invoice_paid.json +61 -0
  97. django_checkouts-1.0.1/tests/gateways/stripe/fixtures/session_open.json +20 -0
  98. django_checkouts-1.0.1/tests/gateways/stripe/fixtures/session_paid.json +20 -0
  99. django_checkouts-1.0.1/tests/gateways/stripe/fixtures/subscription_active.json +45 -0
  100. django_checkouts-1.0.1/tests/gateways/stripe/fixtures/subscription_canceling.json +34 -0
  101. django_checkouts-1.0.1/tests/gateways/stripe/test_checkouts.py +639 -0
  102. django_checkouts-1.0.1/tests/gateways/stripe/test_errors.py +169 -0
  103. django_checkouts-1.0.1/tests/gateways/stripe/test_events.py +157 -0
  104. django_checkouts-1.0.1/tests/gateways/stripe/test_invoices.py +175 -0
  105. django_checkouts-1.0.1/tests/gateways/stripe/test_subscriptions.py +380 -0
  106. django_checkouts-1.0.1/tests/gateways/stripe/test_webhooks.py +239 -0
  107. django_checkouts-1.0.1/tests/test_capabilities.py +68 -0
  108. django_checkouts-1.0.1/tests/test_checks.py +89 -0
  109. django_checkouts-1.0.1/tests/test_client.py +196 -0
  110. django_checkouts-1.0.1/tests/test_contract_suite.py +139 -0
  111. django_checkouts-1.0.1/tests/test_credentials.py +40 -0
  112. django_checkouts-1.0.1/tests/test_drf.py +88 -0
  113. django_checkouts-1.0.1/tests/test_gateway_dispatch.py +140 -0
  114. django_checkouts-1.0.1/tests/test_public_interface.py +210 -0
  115. django_checkouts-1.0.1/tests/test_registry.py +56 -0
  116. django_checkouts-1.0.1/tests/test_types.py +387 -0
  117. django_checkouts-1.0.1/tests/test_webhooks.py +106 -0
  118. django_checkouts-1.0.1/uv.lock +1357 -0
@@ -0,0 +1,73 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions: {}
9
+
10
+ jobs:
11
+ release:
12
+ name: Release Please
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: write
16
+ issues: write
17
+ pull-requests: write
18
+ outputs:
19
+ release_created: ${{ steps.release.outputs.release_created }}
20
+ tag_name: ${{ steps.release.outputs.tag_name }}
21
+ steps:
22
+ - id: release
23
+ uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4
24
+ with:
25
+ config-file: release-please-config.json
26
+ manifest-file: .release-please-manifest.json
27
+
28
+ build:
29
+ name: Build distributions
30
+ needs: release
31
+ if: ${{ needs.release.outputs.release_created == 'true' }}
32
+ runs-on: ubuntu-latest
33
+ permissions:
34
+ contents: read
35
+ steps:
36
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
37
+ with:
38
+ ref: ${{ needs.release.outputs.tag_name }}
39
+ # setuptools_scm calcula a versão a partir da tag do git: sem
40
+ # histórico/tags a build cai no fallback_version "0.0.0".
41
+ fetch-depth: 0
42
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
43
+ with:
44
+ python-version: "3.13"
45
+ - name: Install build tools
46
+ run: python -m pip install "build>=1.2,<2" "twine>=6,<7"
47
+ - name: Build distributions
48
+ run: python -m build
49
+ - name: Validate distributions
50
+ run: python -m twine check dist/*
51
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
52
+ with:
53
+ name: python-package-distributions
54
+ path: dist/
55
+ if-no-files-found: error
56
+
57
+ publish:
58
+ name: Publish distributions to PyPI
59
+ needs: build
60
+ runs-on: ubuntu-latest
61
+ environment:
62
+ name: pypi
63
+ url: https://pypi.org/p/django-checkouts
64
+ permissions:
65
+ id-token: write
66
+ steps:
67
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
68
+ with:
69
+ name: python-package-distributions
70
+ path: dist/
71
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
72
+ with:
73
+ packages-dir: dist/
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ .coverage
8
+ coverage.xml
9
+ htmlcov/
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ django_checkouts/version.py
14
+ docs/_build/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1 @@
1
+ {".":"1.0.1"}
@@ -0,0 +1,148 @@
1
+ # Task 1 — vocabulário normalizado, DTOs e erros
2
+
3
+ ## Implementação
4
+
5
+ - Substituído o vocabulário público de `Provider` por `Gateway`, preservando
6
+ `Provider = Gateway` durante a migração dos módulos legados.
7
+ - Adicionados os enums normalizados de assinatura, fatura, evento, recurso,
8
+ alterações e retry, todos com rótulos traduzíveis via `pgettext_lazy`.
9
+ - Criada a taxonomia pública de erros com `RetryAdvice`, a árvore de erros de
10
+ gateway e aliases compatíveis para `ProviderTemporaryError`,
11
+ `ProviderPermanentError` e `CheckoutNotFound`.
12
+ - Criado `GatewayOptions`, os DTOs públicos de checkout, assinatura, fatura e
13
+ evento, e a reexportação explícita em `django_checkouts.types`.
14
+ - As dataclasses são congeladas, slotted e keyword-only (exceto
15
+ `RetryAdvice`, cuja forma posicional é a indicada pela especificação).
16
+ - Centralizados os validadores de quantidades, moeda, UTC, criação de checkout
17
+ e alterações de assinatura. Resultados e eventos fazem uma cópia defensiva
18
+ de `raw` com `MappingProxyType(deepcopy(dict(...)))` e omitem esse campo do
19
+ `repr` e da comparação.
20
+
21
+ ## TDD
22
+
23
+ 1. RED: criado `tests/test_types.py` com os três testes prescritos (dinheiro,
24
+ imutabilidade/`raw`, quantidade) e executado
25
+ `uv run pytest tests/test_types.py -q`. A coleta falhou como esperado com
26
+ `ImportError: cannot import name 'Gateway'`.
27
+ 2. RED ampliado: antes da implementação, os testes passaram a abranger itens
28
+ vazios/moedas mistas/recorrência incompatível, quantidades booleanas,
29
+ conflitos de alteração, UTC, cópia defensiva de `raw` e `RetryAdvice`.
30
+ A execução continuou falhando pela mesma API inexistente.
31
+ 3. GREEN: após a implementação, `uv run pytest tests/test_types.py
32
+ tests/test_base.py -q --no-cov` passou com **32 testes**.
33
+
34
+ ## Comandos e resultados
35
+
36
+ | Comando | Resultado |
37
+ | --- | --- |
38
+ | `uv run pytest tests/test_types.py -q` (RED) | coleta falhou: `Gateway` ausente |
39
+ | `uv run pytest tests/test_types.py tests/test_base.py -q --no-cov` | 32 passed |
40
+ | `uv run pytest -q` | 102 passed; cobertura total 94,39% |
41
+ | `uv run mypy django_checkouts` | sucesso, sem problemas |
42
+ | `uvx ruff check django_checkouts/enums.py django_checkouts/exceptions.py django_checkouts/gateways django_checkouts/types tests/test_types.py` | sucesso |
43
+ | `git diff --check` | sucesso |
44
+
45
+ O comando literal do brief, sem `--no-cov`, executa os 32 testes com sucesso,
46
+ mas termina com código 1 porque o gate global de 90% de cobertura é aplicado a
47
+ um subconjunto da suíte (64,03%); a suíte completa passa o gate com 94,39%.
48
+
49
+ ## Arquivos alterados
50
+
51
+ - `django_checkouts/enums.py`
52
+ - `django_checkouts/exceptions.py`
53
+ - `django_checkouts/gateways/__init__.py`
54
+ - `django_checkouts/gateways/options.py`
55
+ - `django_checkouts/types/__init__.py`
56
+ - `django_checkouts/types/common.py`
57
+ - `django_checkouts/types/checkouts.py`
58
+ - `django_checkouts/types/subscriptions.py`
59
+ - `django_checkouts/types/invoices.py`
60
+ - `django_checkouts/types/events.py`
61
+ - `tests/test_types.py`
62
+
63
+ ## Auto-revisão
64
+
65
+ - Conferidos os valores de todos os novos enums e os campos de `Checkout`,
66
+ `Subscription`, `Invoice`, `WebhookEvent` e `EventPage` por introspecção de
67
+ dataclasses.
68
+ - Confirmados `Price` e todos os DTOs no `__all__` explícito.
69
+ - Confirmadas as normalizações de moeda e UTC, a imutabilidade de dataclasses,
70
+ a ocultação de `raw` no `repr` e a proteção contra mutação da raiz de `raw`.
71
+ - Mantida compatibilidade de imports legados necessária à suíte atual; não
72
+ foram implementadas tarefas posteriores de gateways/resources.
73
+
74
+ ## Pontos de atenção
75
+
76
+ - `uvx ruff check .` ainda acusa duas violações preexistentes em
77
+ `django_checkouts/registry.py` (`S112` e `BLE001`); elas não pertencem a
78
+ este escopo. O lint de todos os arquivos alterados está limpo.
79
+ - A especificação pede `MappingProxyType` na raiz de `raw`; portanto, valores
80
+ aninhados continuam objetos mutáveis da cópia defensiva, exatamente conforme
81
+ a construção prescrita.
82
+
83
+ ## Correção da revisão — 2026-08-15
84
+
85
+ ### RED/GREEN
86
+
87
+ 1. RED: ampliados `tests/test_types.py` e `tests/test_base.py`; a execução de
88
+ `uv run pytest tests/test_types.py tests/test_base.py -q --no-cov` falhou
89
+ com 5 falhas esperadas: resultados aceitavam `bool`/`float`, `RetryAdvice`
90
+ aceitava argumentos posicionais, `GatewayError` retinha token externo e o
91
+ status financeiro desconhecido ainda levantava `ProviderPermanentError`.
92
+ 2. GREEN: adicionada validação estrita de inteiro a todos os montantes públicos
93
+ de resultado; valores negativos continuam permitidos para créditos e
94
+ reembolsos. `RetryAdvice` passou a ser `frozen`, `slots` e `kw_only`.
95
+ Mensagens externas de gateway são substituídas por diagnóstico seguro antes
96
+ de serem armazenadas. `BaseCheckoutProvider.map_status` agora levanta
97
+ `GatewayProtocolError`; a herança preserva a compatibilidade com o alias
98
+ `ProviderPermanentError`.
99
+
100
+ ### Evidência de testes
101
+
102
+ | Comando | Resultado |
103
+ | --- | --- |
104
+ | `uv run pytest tests/test_types.py tests/test_base.py -q --no-cov` (RED) | 5 failed, 31 passed — falhas esperadas descritas acima |
105
+ | `uv run pytest tests/test_types.py tests/test_base.py -q --no-cov` (GREEN) | 36 passed |
106
+ | `uv run pytest tests/providers/stripe/test_checkout.py -q --no-cov` | 32 passed |
107
+ | `uv run pytest -q` | 106 passed; cobertura total 94,67% |
108
+ | `uv run mypy django_checkouts` | sucesso, sem problemas |
109
+ | `uvx ruff check django_checkouts/exceptions.py django_checkouts/base.py django_checkouts/types tests/test_types.py tests/test_base.py` | sucesso |
110
+ | `git diff --check` | sucesso |
111
+
112
+ ### Auto-revisão da correção
113
+
114
+ - Conferidos os sete campos monetários solicitados: todos recusam `bool` e
115
+ `float`; nenhum impõe positividade nos resultados de gateway.
116
+ - Confirmado que segredos e tokens fornecidos via `gateway_message` não são
117
+ armazenados nem aparecem em `repr`/`str`.
118
+ - Confirmado que o mapeamento desconhecido é `GatewayProtocolError`, mantendo
119
+ captura legada como `ProviderPermanentError` para provedores existentes.
120
+ - Não há preocupações pendentes no escopo desta correção.
121
+
122
+ ## Correção da re-revisão — 2026-08-15
123
+
124
+ ### RED/GREEN
125
+
126
+ 1. RED: adicionados testes públicos para um `gateway_status` contendo uma
127
+ credencial, para `typing.get_type_hints` de todos os DTOs reexportados e de
128
+ `GatewayOptions`, e para os três campos datetime obrigatórios de eventos.
129
+ `uv run pytest tests/test_types.py tests/test_base.py -q --no-cov` falhou
130
+ com 5 falhas esperadas: o status externo aparecia na exceção, `Mapping` não
131
+ era resolvível durante a introspecção e os três valores `None` eram aceitos.
132
+ 2. GREEN: `map_status` mantém a orientação para atualizar `STATUS_MAP`, mas
133
+ não inclui o valor remoto na mensagem. As dependências das anotações dos
134
+ DTOs e de `GatewayOptions` agora são importadas em runtime, preservando
135
+ `from __future__ import annotations`. Foi criado `normalize_required_utc`,
136
+ reutilizando a normalização UTC existente e recusando `None` nos três
137
+ campos obrigatórios.
138
+
139
+ ### Evidência de testes
140
+
141
+ | Comando | Resultado |
142
+ | --- | --- |
143
+ | `uv run pytest tests/test_types.py tests/test_base.py -q --no-cov` (RED) | 5 failed, 36 passed — falhas esperadas descritas acima |
144
+ | `uv run pytest tests/test_types.py tests/test_base.py -q --no-cov` (GREEN) | 41 passed |
145
+ | `uv run pytest -q` | 111 passed; cobertura total 95,91% |
146
+ | `uv run mypy django_checkouts` | sucesso, sem problemas |
147
+ | `uvx ruff check django_checkouts/base.py django_checkouts/types django_checkouts/gateways/options.py tests/test_types.py tests/test_base.py` | sucesso |
148
+ | `git diff --check` | sucesso |
@@ -0,0 +1,68 @@
1
+ # Task 2 — capabilities e despacho tipado
2
+
3
+ ## Escopo entregue
4
+
5
+ - Capabilities aninhadas, `frozen` e com `slots`; os conjuntos são normalizados
6
+ para `frozenset` e o mapa de meios de pagamento é protegido por
7
+ `MappingProxyType`.
8
+ - Os dez comandos internos tipados e os contratos `ExecutionContext` e
9
+ `CommandHandler`.
10
+ - `BaseCheckoutGateway` com índice de handlers por tipo **exato**, validação antes
11
+ do contexto de I/O, tradução sanitizada de exceções externas e `check()` vazio.
12
+ - Testes de capabilities, despacho exato, ausência de handler, validação pré-I/O,
13
+ propagação da variante e sanitização do erro externo.
14
+
15
+ ## Evidência TDD
16
+
17
+ 1. **RED** — após criar `tests/test_capabilities.py` e
18
+ `tests/test_gateway_dispatch.py`, executei:
19
+
20
+ ```console
21
+ uv run pytest tests/test_capabilities.py tests/test_gateway_dispatch.py -q
22
+ ```
23
+
24
+ A coleta falhou como esperado com `ModuleNotFoundError` para
25
+ `django_checkouts.capabilities`, pois os módulos de Task 2 ainda não existiam.
26
+ 2. **GREEN** — implementei somente os módulos previstos no briefing. A rodada
27
+ focada sem cobertura passou: `7 passed in 0.08s`.
28
+ 3. **REFACTOR** — a anotação da coleção de handlers passou a usar
29
+ `CommandHandler[Any]`, permitindo handlers concretos de diferentes resultados
30
+ sem relaxar o despacho exato em runtime. A rodada focada, Ruff e mypy foram
31
+ repetidos após a alteração.
32
+
33
+ ## Arquivos alterados
34
+
35
+ - `django_checkouts/capabilities.py`
36
+ - `django_checkouts/gateways/commands.py`
37
+ - `django_checkouts/gateways/contracts.py`
38
+ - `django_checkouts/gateways/base.py`
39
+ - `tests/test_capabilities.py`
40
+ - `tests/test_gateway_dispatch.py`
41
+
42
+ ## Verificação
43
+
44
+ ```console
45
+ uv run pytest tests/test_capabilities.py tests/test_gateway_dispatch.py -q --no-cov
46
+ # 7 passed
47
+
48
+ uvx ruff check django_checkouts/capabilities.py django_checkouts/gateways tests/test_capabilities.py tests/test_gateway_dispatch.py
49
+ # All checks passed!
50
+
51
+ uv run mypy django_checkouts
52
+ # Success: no issues found in 27 source files
53
+
54
+ uv run pytest
55
+ # 118 passed; cobertura total 96.31%
56
+ ```
57
+
58
+ ## Auto-revisão e ressalvas
59
+
60
+ - O despacho usa `type(command)`, portanto subclasses não reutilizam handlers de
61
+ comandos-base por engano.
62
+ - `validate()` é chamado antes de criar o contexto e antes de qualquer handler
63
+ poder chamar `context.call`; comandos inválidos e sem handler não fazem I/O.
64
+ - `call()` preserva apenas `CheckoutError`; demais `Exception` viram
65
+ `GatewayPermanentError` sem expor a mensagem externa.
66
+ - Não foram implementados handlers ou gateways concretos, que pertencem às tarefas
67
+ posteriores. Handlers concretos devem fazer I/O exclusivamente via
68
+ `ExecutionContext.call` para conservar a tradução de erros públicos.
@@ -0,0 +1,99 @@
1
+ # Task 8 report — release surface, documentation, and delivery
2
+
3
+ ## Scope delivered
4
+
5
+ - Replaced the package root with the exact public exports `CheckoutClient`,
6
+ `Gateway`, and `get_checkout_gateway`.
7
+ - Added `tests/test_public_interface.py` to lock exact root exports, all ten
8
+ resource method signatures, statically checked result types via `assert_type`,
9
+ absence of Stripe SDK names in normalized public annotations, and absence of
10
+ the pre-1.0 modules and compatibility exports.
11
+ - Removed the pre-1.0 base, DTO, authentication, Stripe implementation, registry
12
+ compatibility, enums, errors, checks alias, fixtures, and tests. The retained
13
+ low-level webhook helpers now use gateway terminology consistently.
14
+ - Updated `test_settings.py` to configure
15
+ `django_checkouts.gateways.stripe.StripeGateway`.
16
+ - Replaced the README with the resource-oriented quickstart, explicit gateway
17
+ implementation status, persistence boundary, delivery commands, and
18
+ BSD-3-Clause notice. Added the 1.0 changelog.
19
+ - Added a warning-clean Sphinx site with configuration plus quickstart,
20
+ subscriptions, webhooks, reconciliation, errors/retries, typed gateway
21
+ options, gateway authoring, and complete pre-1.0 migration pages. Every
22
+ public page states that the library owns no persistence.
23
+ - Documented absolute seat quantities, all proration and cancellation choices,
24
+ resume semantics, raw-body verification, `(variant, event_id)` uniqueness,
25
+ half-open reconciliation windows with overlap/deduplication, all four retry
26
+ dispositions, Stripe portability limits, and the gateway contract suite.
27
+
28
+ ## TDD evidence
29
+
30
+ The structural test was written before production removal. The prescribed RED
31
+ run:
32
+
33
+ ```text
34
+ uv run pytest tests/test_public_interface.py -q
35
+ # 2 failed, 2 passed
36
+ ```
37
+
38
+ The failures were the intended ones: the root still exported only the old
39
+ factory and the old modules were still importable. Exact resource signatures
40
+ and the Stripe-annotation boundary already passed. After replacing the surface:
41
+
42
+ ```text
43
+ uv run pytest tests/test_public_interface.py -q --no-cov
44
+ # 4 passed
45
+ uv run mypy tests/test_public_interface.py
46
+ # Success: no issues found in 1 source file
47
+ ```
48
+
49
+ The direct mypy run proves that every `assert_type` result contract is checked
50
+ statically, in addition to the runtime structural assertions.
51
+
52
+ ## Fresh delivery verification
53
+
54
+ | Command | Result |
55
+ | --- | --- |
56
+ | `uv sync --all-extras --all-groups` | resolved 60 packages; checked 57 |
57
+ | `uv run pytest` | 196 passed; 95.36% coverage |
58
+ | `uv run mypy django_checkouts` | success across 45 source files |
59
+ | `uv run mypy tests/test_public_interface.py` | success |
60
+ | `uvx ruff check .` | all checks passed |
61
+ | `uv run sphinx-build -W -b html docs docs/_build/html` | build succeeded with warnings as errors |
62
+ | `uv build` | wheel and sdist built successfully |
63
+ | legacy-term scan | matches only `docs/migration-pre-1.0.rst` |
64
+ | public Stripe-annotation scan | no output |
65
+ | `git diff --check` | success |
66
+
67
+ Both a universal wheel and source distribution exist in `dist/`. A second
68
+ post-commit build derives their version from the clean immutable release commit,
69
+ without a dirty-tree suffix.
70
+
71
+ ## Clean wheel installation
72
+
73
+ Installed the built wheel plus Stripe into a new `uv` virtual environment
74
+ outside the repository. With Django 5.2.17 and Stripe 15.5.0, the smoke test:
75
+
76
+ - imported the three exact package exports and `StripeGateway`;
77
+ - confirmed every removed module is undiscoverable;
78
+ - confirmed `py.typed` and the packaged BSD license;
79
+ - confirmed no removed Stripe package path exists in the wheel.
80
+
81
+ The temporary environment was deleted after the successful check.
82
+
83
+ The clean-tree wheel smoke test also asserted that its embedded setuptools-scm
84
+ version contains the release commit revision.
85
+
86
+ ## Design notes
87
+
88
+ - The tests tied exclusively to deleted modules were removed with those modules;
89
+ the complete gateway lifecycle and contract suites remain and pass.
90
+ - The low-level `django_checkouts.webhooks` utilities were retained because the
91
+ Task 8 deletion list did not remove that module. Their constructor vocabulary
92
+ was changed from `provider` to `gateway`, eliminating the compatibility
93
+ surface and satisfying the release scan.
94
+ - `default_app_config` was removed from the root because modern supported Django
95
+ versions discover `DjangoCheckoutsConfig` directly.
96
+
97
+ ## Concerns
98
+
99
+ None.
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ Todas as mudanças relevantes deste projeto são registradas aqui.
4
+
5
+ ## [1.0.1](https://github.com/davisilvarafacho/django-checkouts/compare/v1.0.0...v1.0.1) (2026-09-05)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * alinhar sublinhado do titulo no README.rst ([e885ac9](https://github.com/davisilvarafacho/django-checkouts/commit/e885ac970f754b1b1d808b5ac6445b7306a4cce6))
11
+ * alinhar sublinhado do título no README.rst ([542511e](https://github.com/davisilvarafacho/django-checkouts/commit/542511e1c1eab4153dac23ee60565919b3465b73))
12
+
13
+ ## 1.0.0 (2026-09-05)
14
+
15
+
16
+ ### Features
17
+
18
+ * add resource-oriented checkout client ([f047bdd](https://github.com/davisilvarafacho/django-checkouts/commit/f047bdded3f73f595ca02b62a3250b7309b22cbc))
19
+ * add Stripe recurring lifecycle ([c1b5449](https://github.com/davisilvarafacho/django-checkouts/commit/c1b54498c20cb4edf20a852cb2db4b0bfaf39a0c))
20
+ * add Stripe webhooks and reconciliation ([2fd4706](https://github.com/davisilvarafacho/django-checkouts/commit/2fd4706d777e926dbfca575d901f852fed66df92))
21
+ * add typed gateway dispatch ([c4c981e](https://github.com/davisilvarafacho/django-checkouts/commit/c4c981e0f8f2cc304e2f741fee9d1c607fc4ee91))
22
+ * adicionar setup de forma de pagamento ([83c1fb5](https://github.com/davisilvarafacho/django-checkouts/commit/83c1fb546b9269ed40af0dbb16d2492386d8208c))
23
+ * define normalized checkout contracts ([cb5a15e](https://github.com/davisilvarafacho/django-checkouts/commit/cb5a15ec6ba68b4e7e95cc815def27c05629195c))
24
+ * expor fatos financeiros de faturas Stripe ([4695875](https://github.com/davisilvarafacho/django-checkouts/commit/4695875a903e1ce4b628b41837efb86eefed7e8a))
25
+ * port Stripe checkout gateway ([ea47443](https://github.com/davisilvarafacho/django-checkouts/commit/ea47443512288400175d063cec9c351780f7395b))
26
+ * publish gateway integration contracts ([dddca1c](https://github.com/davisilvarafacho/django-checkouts/commit/dddca1c9aee029318375c1e8c26040a75102e819))
27
+ * publish recurring gateway interface ([733964a](https://github.com/davisilvarafacho/django-checkouts/commit/733964ae0cd9b253ac8c3a75dd83e205fd3bcbc2))
28
+
29
+
30
+ ### Bug Fixes
31
+
32
+ * completar ciclo de setup de pagamento ([3c0b988](https://github.com/davisilvarafacho/django-checkouts/commit/3c0b988c9d8a78b3fd2723c939c131787f3d0d73))
33
+ * deeply normalize Stripe checkout responses ([d76b0b5](https://github.com/davisilvarafacho/django-checkouts/commit/d76b0b5c5be2cc758df54970de2cf1d7654e8d4a))
34
+ * harden normalized gateway contracts ([9a4fdd9](https://github.com/davisilvarafacho/django-checkouts/commit/9a4fdd90072ad7686277960eff78c03223af7eb8))
35
+ * normalizar eventos de setup ([ce17107](https://github.com/davisilvarafacho/django-checkouts/commit/ce17107186e2153b72efb4e9e5a432e67d7f48a1))
36
+ * normalize Stripe SDK checkout responses ([30d0af7](https://github.com/davisilvarafacho/django-checkouts/commit/30d0af77eada641a7d75eeda7a05303beb345f33))
37
+ * reject Stripe inline subscription prices ([3236c2c](https://github.com/davisilvarafacho/django-checkouts/commit/3236c2c4687538219dc8e461dd6788b688ff7da9))
38
+
39
+ ## 1.0.0
40
+
41
+ - Publica `CheckoutClient`, `Gateway` e `get_checkout_gateway` como entradas
42
+ oficiais do pacote.
43
+ - Adiciona recursos tipados para checkouts, assinaturas, faturas, webhooks e
44
+ reconciliação de eventos.
45
+ - Completa o ciclo Stripe de checkout e cobrança recorrente, com opções
46
+ específicas tipadas e tradução pública de erros.
47
+ - Publica integração opcional com DRF, fake determinístico e suíte de contrato
48
+ para gateways externos.
49
+ - Remove a superfície experimental anterior a 1.0; consulte o guia de migração.
50
+ - Mantém o projeto sob a licença BSD-3-Clause.
@@ -0,0 +1,133 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## O que é
6
+
7
+ `django-checkouts` — biblioteca (não projeto Django) que dá uma interface única e tipada
8
+ para checkout hospedado de Stripe, PagBank e Asaas, com foco no mercado brasileiro.
9
+ **MVP em construção:** só o Stripe está implementado; PagBank e Asaas ainda não existem
10
+ como código (só como docs de pesquisa em `docs/`).
11
+
12
+ A lib **não tem models, views, urls nem migrations** — ela nunca toca o banco. Recebe
13
+ argumentos simples, devolve dataclasses normalizados; a persistência é do projeto que a usa.
14
+
15
+ ## Comandos
16
+
17
+ ```console
18
+ uv sync --all-extras # ambiente (uv >= 0.11.28, Python 3.12 via .python-version)
19
+ uv run pytest # suíte completa; gate de 90% de cobertura
20
+ uv run pytest tests/providers/stripe/test_checkout.py::TestCreateCheckout::test_builds_inline_price_data
21
+ uv run pytest -q --no-cov # rodada rápida sem o relatório de cobertura
22
+ uv run mypy django_checkouts
23
+ uvx ruff check .
24
+ ```
25
+
26
+ `pytest` usa `test_settings.py` como `DJANGO_SETTINGS_MODULE` (sqlite em memória, uma
27
+ variante `stripe` já configurada com credenciais falsas).
28
+
29
+ ## Arquitetura
30
+
31
+ ### O contrato dos providers (`base.py`)
32
+
33
+ `BaseCheckoutProvider` divide público de protegido, e a divisão é o coração da lib:
34
+
35
+ - **Público e concreto** (`create_checkout`, `retrieve_checkout`, `cancel_checkout`,
36
+ `verify_webhook`) — vive na base, valida tudo o que dá para validar sem rede e levanta
37
+ `ValidationError`/`CapabilityNotSupported` **antes de qualquer I/O**.
38
+ - **Protegido e abstrato** (`_create_checkout`, `_retrieve_checkout`, `_cancel_checkout`,
39
+ `_parse_webhook`) — é o que um provider novo escreve. Recebe um `CheckoutRequest` já
40
+ validado e pode confiar nele.
41
+
42
+ `build_request()` é o funil de validação; `CheckoutRequest` existe justamente para que
43
+ acrescentar um campo em `create_checkout` não mude a assinatura de todos os providers.
44
+
45
+ ### Registry e configuração (`registry.py`)
46
+
47
+ Providers são "variantes nomeadas" em `settings.CHECKOUT_VARIANTS`:
48
+ `{"stripe": ("caminho.pontilhado.Classe", {**kwargs_do_construtor})}`.
49
+ `get_checkout_provider("stripe")` cacheia a instância em `PROVIDER_CACHE`;
50
+ `get_checkout_provider("asaas", api_key=...)` (com overrides) **não** cacheia — é o caso
51
+ multi-tenant. `conftest.py` limpa o cache entre testes por autouse fixture.
52
+
53
+ ### As duas direções de autenticação
54
+
55
+ Fácil de confundir; são módulos separados de propósito:
56
+
57
+ - `credentials.py` — **saída** (app → gateway). `BaseAuth`/`TokenAuth` aplicam o header de
58
+ API numa sessão de requests. Token com `repr=False` para não vazar em traceback.
59
+ - `webhooks.py` — **entrada** (gateway → app). `BaseWebhookAuth` prova a origem e devolve
60
+ o payload já decodificado. Comparação de segredo sempre com `hmac.compare_digest`.
61
+ `HeaderTokenWebhookAuth` (modelo Asaas) e `Sha256BodyWebhookAuth` (modelo PagBank) já
62
+ existem, sem provider que as use ainda.
63
+
64
+ Ambos expõem `validate() -> list[CheckMessage]`, e `checks.py` transforma isso em saída do
65
+ `manage.py check` — credencial ausente vira erro de deploy, não 401 no primeiro cliente.
66
+
67
+ `authentication.py` é a camada DRF opcional: `webhook_auth_for(Provider.STRIPE)` fabrica a
68
+ authentication class, e o `WebhookPayload` verificado chega em `request.auth`. O
69
+ `request.user` vira um `FakeGatewayUser` — usuário falso, sem banco, que só existe porque o
70
+ DRF exige o par `(user, auth)`.
71
+
72
+ ### Invariantes que atravessam tudo
73
+
74
+ - **Dinheiro é sempre `int` em centavos.** R$ 49,90 é `4990`. `amount_decimal` /
75
+ `amount_total_decimal` existem só para exibição.
76
+ - **Todo objeto de resposta carrega `raw`** — o payload cru intacto. Para visão tipada do
77
+ cru, cada provider declara uma subclasse de `ProviderState` (`from_raw` descarta chaves
78
+ desconhecidas, então campo novo do provedor não quebra nada).
79
+ - **Corpo cru de webhook, sempre.** Reserializar o JSON invalida a assinatura do Stripe e o
80
+ hash do PagBank.
81
+ - **`map_status` levanta no desconhecido; `map_event` devolve `None`.** Não há
82
+ `CheckoutStatus.UNKNOWN`: um status não mapeado circularia como estado legítimo e só
83
+ apareceria na conciliação de dinheiro. Evento desconhecido é o oposto — os provedores
84
+ emitem dezenas de eventos sem relação com checkout, e derrubar o webhook por causa deles
85
+ travaria a fila de notificações da conta.
86
+ - **Nada que não seja `CheckoutError` sai de um provider.** Se `requests.Timeout` ou
87
+ `KeyError` vazar, é bug. A divisão `ProviderTemporaryError` vs `ProviderPermanentError`
88
+ carrega a semântica de retry no próprio tipo.
89
+ - **Capacidades opcionais são declaradas, não improvisadas.** `CAPABILITIES`,
90
+ `SUPPORTED_PAYMENT_METHODS` e `SUPPORTED_CYCLES` fazem a base recusar de forma uniforme,
91
+ antes da rede, em vez de cada provider levantar `NotImplementedError` do seu jeito.
92
+ - **`BillingCycle` tem opções fechadas** (nomes seguem os do Asaas, o vocabulário mais
93
+ restrito dos três). Expor o par `interval`+`interval_count` do Stripe não sobreviveria à
94
+ tradução. Cada provider converte: `QUARTERLY` → `("month", 3)` no Stripe.
95
+
96
+ ### Escrever um provider novo
97
+
98
+ Subclassear `BaseCheckoutProvider`; definir `name`, `STATUS_MAP`, `EVENT_MAP`,
99
+ `CAPABILITIES`, `SUPPORTED_PAYMENT_METHODS`, `SUPPORTED_CYCLES`, `state_class`; no
100
+ `__init__` montar `self.auth` e `self.webhook_auth`; implementar os quatro métodos
101
+ protegidos. O Stripe (`providers/stripe/checkout.py`) é o modelo de referência — inclusive
102
+ do `_call()`, que traduz os erros do SDK para a taxonomia da lib. Note que o `STATUS_MAP`
103
+ dele é chaveado por `f"{status}/{payment_status}"`, porque no Stripe "a sessão terminou" e
104
+ "o dinheiro entrou" são campos diferentes.
105
+
106
+ ## Convenções
107
+
108
+ - **Português (pt-BR) em docstrings, mensagens de erro e comentários.** As mensagens são
109
+ longas de propósito: dizem o que fazer, não só o que falhou. Mantenha esse tom.
110
+ - Enums são `models.TextChoices` com `pgettext_lazy` — serializam como string direto num
111
+ campo de model e o rótulo é traduzível.
112
+ - **Identidade de provedor é `Provider`, nunca string solta.** `name = Provider.STRIPE`,
113
+ `get_checkout_provider(Provider.STRIPE)`, `webhook_auth_for(Provider.PAGSEGURO)`. Como é
114
+ `TextChoices`, o membro casa com a chave string do `CHECKOUT_VARIANTS` em `settings` —
115
+ e o settings do usuário continua com string crua, porque um módulo de settings não deve
116
+ importar a lib. String crua no código só para variante com nome próprio (`"stripe-br"`,
117
+ multi-tenant); por isso as anotações são `Provider | str`.
118
+ - Ruff: um import por linha (`force-single-line`) e `from __future__ import annotations`
119
+ obrigatório no topo de todo módulo, inclusive testes.
120
+ - Nenhum teste toca a rede: SDK e respostas HTTP são substituídos, com fixtures de payload
121
+ real em `tests/providers/<provider>/fixtures/`. A fixture `load_fixture` do `conftest.py`
122
+ resolve o caminho ao lado do módulo de teste.
123
+ - Versão vem do `setuptools_scm` (tag git) e é escrita em `django_checkouts/version.py`,
124
+ que é gitignored.
125
+
126
+ ## `docs/`
127
+
128
+ Notas de pesquisa, não documentação de usuário: `integracao-sandbox-{stripe,pagseguro,asaas}.md`
129
+ levantam as particularidades de cada API (credenciais, sandbox, formato de webhook) e
130
+ marcam com ⚠️ o que **não** foi confirmado na fonte oficial. `plano-testes-sandbox.md`
131
+ descreve uma suíte contra sandbox real, ainda não implementada. Ao implementar PagBank ou
132
+ Asaas, esses arquivos são o ponto de partida — e os itens ⚠️ precisam de validação antes de
133
+ virarem código.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Davi Rafacho
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.