trustrender 0.3.2__tar.gz → 0.3.3__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 (86) hide show
  1. {trustrender-0.3.2/src/trustrender.egg-info → trustrender-0.3.3}/PKG-INFO +82 -44
  2. {trustrender-0.3.2 → trustrender-0.3.3}/README.md +81 -43
  3. {trustrender-0.3.2 → trustrender-0.3.3}/pyproject.toml +1 -1
  4. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/__init__.py +1 -1
  5. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/adapters/shopify.py +9 -2
  6. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/adapters/stripe.py +10 -0
  7. {trustrender-0.3.2 → trustrender-0.3.3/src/trustrender.egg-info}/PKG-INFO +82 -44
  8. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_adapters.py +41 -0
  9. {trustrender-0.3.2 → trustrender-0.3.3}/LICENSE +0 -0
  10. {trustrender-0.3.2 → trustrender-0.3.3}/setup.cfg +0 -0
  11. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/adapters/__init__.py +0 -0
  12. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/einvoice.j2.typ +0 -0
  13. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/einvoice_data.json +0 -0
  14. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/invoice.j2.typ +0 -0
  15. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/invoice_data.json +0 -0
  16. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/letter.j2.typ +0 -0
  17. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/letter_data.json +0 -0
  18. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/receipt.j2.typ +0 -0
  19. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/receipt_data.json +0 -0
  20. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/report.j2.typ +0 -0
  21. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/report_data.json +0 -0
  22. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/statement.j2.typ +0 -0
  23. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/builtin_templates/statement_data.json +0 -0
  24. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/cli.py +0 -0
  25. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/contract.py +0 -0
  26. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/dashboard.py +0 -0
  27. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/doctor.py +0 -0
  28. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/engine.py +0 -0
  29. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/errors.py +0 -0
  30. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/filters.py +0 -0
  31. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/fingerprint.py +0 -0
  32. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/fonts/Inter/Inter-Bold.ttf +0 -0
  33. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/fonts/Inter/Inter-BoldItalic.ttf +0 -0
  34. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/fonts/Inter/Inter-Italic.ttf +0 -0
  35. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/fonts/Inter/Inter-Regular.ttf +0 -0
  36. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/fonts/Inter/LICENSE.txt +0 -0
  37. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/invoice_aliases.py +0 -0
  38. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/invoice_ingest.py +0 -0
  39. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/invoice_schema.py +0 -0
  40. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/playground/assets/index-Bsf6YnD1.css +0 -0
  41. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/playground/assets/index-C6K0JsLw.js +0 -0
  42. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/playground/assets/pdf.worker-C1breYqn.mjs +0 -0
  43. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/playground/favicon.svg +0 -0
  44. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/playground/icons.svg +0 -0
  45. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/playground/index.html +0 -0
  46. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/provenance.py +0 -0
  47. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/readiness.py +0 -0
  48. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/regression.py +0 -0
  49. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/semantic.py +0 -0
  50. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/server.py +0 -0
  51. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/templates.py +0 -0
  52. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/trace.py +0 -0
  53. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender/zugferd.py +0 -0
  54. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender.egg-info/SOURCES.txt +0 -0
  55. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender.egg-info/dependency_links.txt +0 -0
  56. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender.egg-info/entry_points.txt +0 -0
  57. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender.egg-info/requires.txt +0 -0
  58. {trustrender-0.3.2 → trustrender-0.3.3}/src/trustrender.egg-info/top_level.txt +0 -0
  59. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_adversarial_corpus.py +0 -0
  60. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_audit_e2e.py +0 -0
  61. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_cli.py +0 -0
  62. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_contract.py +0 -0
  63. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_doctor.py +0 -0
  64. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_engine.py +0 -0
  65. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_error_pipeline.py +0 -0
  66. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_errors.py +0 -0
  67. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_filters.py +0 -0
  68. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_fingerprint.py +0 -0
  69. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_fingerprint_stress.py +0 -0
  70. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_fonts.py +0 -0
  71. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_invoice_ingest.py +0 -0
  72. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_pagination.py +0 -0
  73. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_provenance.py +0 -0
  74. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_readiness.py +0 -0
  75. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_real_payloads.py +0 -0
  76. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_regression.py +0 -0
  77. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_regression_stress.py +0 -0
  78. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_render.py +0 -0
  79. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_semantic.py +0 -0
  80. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_semantic_stress.py +0 -0
  81. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_server.py +0 -0
  82. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_templates.py +0 -0
  83. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_trace.py +0 -0
  84. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_ugly_data.py +0 -0
  85. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_ugly_data_pressure.py +0 -0
  86. {trustrender-0.3.2 → trustrender-0.3.3}/tests/test_zugferd.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trustrender
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Validate and normalize Stripe, Shopify, and custom billing data for Factur-X/ZUGFeRD
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://trustrender.dev
@@ -42,56 +42,90 @@ Dynamic: license-file
42
42
 
43
43
  # TrustRender
44
44
 
45
- Validate and normalize billing data from Stripe, Shopify, and custom systems before Factur-X/ZUGFeRD embedding.
45
+ Validate and normalize billing data before Factur-X/ZUGFeRD embedding.
46
46
 
47
- If you're bridging a non-compliant billing platform into EU e-invoicing, TrustRender adds a validation and normalization layer before tools like [factur-x](https://github.com/akretion/factur-x) and [drafthorse](https://github.com/pretix/python-drafthorse) generate or embed compliant XML. It catches arithmetic mismatches, field misalignment, missing required fields, and structural problems before handoff.
47
+ ## Quick start
48
48
 
49
49
  ```
50
50
  pip install trustrender
51
- trustrender validate invoice.json
52
51
  ```
53
52
 
54
- ## What it does
53
+ Stripe and Shopify billing exports don't include the seller fields required for compliant invoices. TrustRender catches this:
55
54
 
56
- Takes invoice JSON from Stripe, Shopify, custom billing APIs, or legacy exports and tells you whether it's safe to embed as Factur-X/ZUGFeRD.
55
+ ```
56
+ $ trustrender validate examples/demo_stripe.json --source stripe
57
+
58
+ Invoice: INV-2026-0187
59
+ From:
60
+ To: Rheingold Maschinenbau GmbH
61
+ Items: 3
62
+ Total: $2,685.37
63
+
64
+ BLOCKED — 1 problem(s)
65
+
66
+ Missing vendor/sender name
67
+ Add a sender.name field to your invoice data.
68
+
69
+ This invoice cannot be processed until the problems above are fixed.
70
+ ```
57
71
 
58
72
  ```
59
- $ trustrender validate quickbooks_invoice.json
73
+ $ trustrender validate examples/demo_shopify.json --source shopify
74
+
75
+ Invoice: 1047
76
+ From:
77
+ To: Klaus Berger
78
+ Items: 3
79
+ Total: $1,309.00
80
+
81
+ BLOCKED — 1 problem(s)
82
+
83
+ Missing vendor/sender name
84
+ Add a sender.name field to your invoice data.
85
+
86
+ This invoice cannot be processed until the problems above are fixed.
87
+ ```
60
88
 
61
- Invoice: INV-2026-5541
62
- From: Summit Analytics Co.
63
- To: Horizon Financial
64
- Items: 2
65
- Total: $10,524.50
89
+ Add your seller identity to the source payload and it passes:
66
90
 
67
- Normalizations (25):
68
- DocNumber invoice_number CompanyName sender.name
69
- TxnDate → invoice_date customer.Name → recipient.name
70
- Line → items SubTotal → subtotal
71
- ... and 19 more
91
+ ```
92
+ $ trustrender validate examples/demo_stripe_ready.json --source stripe
93
+
94
+ Invoice: INV-2026-0187
95
+ From: NovaTech Solutions GmbH
96
+ To: Rheingold Maschinenbau GmbH
97
+ Items: 3
98
+ Total: $2,685.37
72
99
 
73
100
  PASS — invoice data is valid
74
101
 
75
102
  Safe to embed in Factur-X/ZUGFeRD PDF.
76
103
  ```
77
104
 
78
- Bad data gets blocked:
79
-
80
105
  ```
81
- $ trustrender validate ocr_extracted_invoice.json
106
+ $ trustrender validate examples/demo_shopify_ready.json --source shopify
82
107
 
83
- BLOCKED — 2 problem(s)
108
+ Invoice: 1047
109
+ From: Werkzeug-Kontor GmbH
110
+ To: Klaus Berger
111
+ Items: 3
112
+ Total: $1,309.00
84
113
 
85
- items[1] total is wrong
86
- You entered $459.00 but math says $450.00
87
- Fix the line total or the price/quantity.
114
+ PASS invoice data is valid
88
115
 
89
- Subtotal is wrong
90
- Lines add up to $12,459.00 but you listed $12,450.00
116
+ Safe to embed in Factur-X/ZUGFeRD PDF.
117
+ ```
91
118
 
92
- This invoice cannot be processed until the problems above are fixed.
119
+ The only difference between the blocked and passing files is one added field:
120
+
121
+ ```json
122
+ "sender": { "name": "NovaTech Solutions GmbH" }
93
123
  ```
94
124
 
125
+ ## Why this exists
126
+
127
+ Stripe and Shopify billing exports are missing seller fields, use platform-specific formats (cents, Unix timestamps, decimal strings), and have no concept of tax compliance. [factur-x](https://github.com/akretion/factur-x) and [drafthorse](https://github.com/pretix/python-drafthorse) generate compliant Factur-X/ZUGFeRD XML, but they assume clean input. TrustRender validates and normalizes source billing data before handoff — it catches arithmetic mismatches, missing required fields, and structural problems so they don't silently produce non-compliant documents.
128
+
95
129
  ## Install
96
130
 
97
131
  ```
@@ -113,20 +147,14 @@ Requires Python 3.11+.
113
147
 
114
148
  ```python
115
149
  from trustrender import validate_invoice
150
+ from trustrender.adapters import from_stripe
116
151
 
117
- result = validate_invoice({
118
- "invoiceNo": "INV-001",
119
- "vendor": {"companyName": "Acme Corp"},
120
- "customer": {"Name": "Client Inc"},
121
- "LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
122
- "SubTotal": 100,
123
- "tax": 8.50,
124
- "TotalAmt": 108.50,
125
- }, zugferd=True)
152
+ # Raw Stripe API response → validated canonical invoice
153
+ result = validate_invoice(from_stripe(raw_stripe_response), zugferd=True)
126
154
 
127
155
  if result["render_ready"] and result.get("zugferd_ready"):
128
- # safe to call factur-x / drafthorse
129
- print("All checks passed")
156
+ canonical = result["canonical"]
157
+ # safe to hand off to factur-x / drafthorse
130
158
  else:
131
159
  for error in result["errors"]:
132
160
  print(f"BLOCKED: {error['message']}")
@@ -141,6 +169,20 @@ else:
141
169
  - `normalizations`: field-level provenance (what was renamed, coerced, computed)
142
170
  - `zugferd_ready`: bool (if `zugferd=True`)
143
171
 
172
+ Also works with messy data from any source:
173
+
174
+ ```python
175
+ result = validate_invoice({
176
+ "invoiceNo": "INV-001",
177
+ "vendor": {"companyName": "Acme Corp"},
178
+ "customer": {"Name": "Client Inc"},
179
+ "LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
180
+ "SubTotal": 100,
181
+ "tax": 8.50,
182
+ "TotalAmt": 108.50,
183
+ }, zugferd=True)
184
+ ```
185
+
144
186
  ## Stripe adapter
145
187
 
146
188
  Raw Stripe Invoice API responses use cents, Unix timestamps, and nested structures. The adapter handles all of it:
@@ -150,13 +192,11 @@ trustrender validate stripe_invoice.json --source stripe --zugferd
150
192
  ```
151
193
 
152
194
  ```python
153
- from trustrender import validate_invoice
154
195
  from trustrender.adapters import from_stripe
155
-
156
196
  result = validate_invoice(from_stripe(raw_stripe_response), zugferd=True)
157
197
  ```
158
198
 
159
- The adapter converts cents to dollars, timestamps to dates, extracts line items from `lines.data[]`, and maps customer fields to recipient. Seller info is not included in Stripe invoices TrustRender will flag it if required for ZUGFeRD compliance.
199
+ Converts cents to dollars, timestamps to dates, extracts line items from `lines.data[]`, maps customer fields to recipient. If you enrich the source payload with `sender`, `vendor`, or `seller`, the adapter passes it through.
160
200
 
161
201
  ## Shopify adapter
162
202
 
@@ -167,13 +207,11 @@ trustrender validate shopify_order.json --source shopify
167
207
  ```
168
208
 
169
209
  ```python
170
- from trustrender import validate_invoice
171
210
  from trustrender.adapters import from_shopify
172
-
173
211
  result = validate_invoice(from_shopify(raw_shopify_order))
174
212
  ```
175
213
 
176
- The adapter parses string amounts to floats, combines first_name + last_name, maps order fields to invoice structure, and preserves structured address fields. Shopify orders have no seller info or due date — TrustRender handles both correctly.
214
+ Parses string amounts to floats, combines first_name + last_name, maps order fields to invoice structure, preserves structured address fields. Shopify orders have no seller info or due date — TrustRender flags the missing seller and handles the absent due date correctly.
177
215
 
178
216
  ## What it normalizes
179
217
 
@@ -1,55 +1,89 @@
1
1
  # TrustRender
2
2
 
3
- Validate and normalize billing data from Stripe, Shopify, and custom systems before Factur-X/ZUGFeRD embedding.
3
+ Validate and normalize billing data before Factur-X/ZUGFeRD embedding.
4
4
 
5
- If you're bridging a non-compliant billing platform into EU e-invoicing, TrustRender adds a validation and normalization layer before tools like [factur-x](https://github.com/akretion/factur-x) and [drafthorse](https://github.com/pretix/python-drafthorse) generate or embed compliant XML. It catches arithmetic mismatches, field misalignment, missing required fields, and structural problems before handoff.
5
+ ## Quick start
6
6
 
7
7
  ```
8
8
  pip install trustrender
9
- trustrender validate invoice.json
10
9
  ```
11
10
 
12
- ## What it does
11
+ Stripe and Shopify billing exports don't include the seller fields required for compliant invoices. TrustRender catches this:
13
12
 
14
- Takes invoice JSON from Stripe, Shopify, custom billing APIs, or legacy exports and tells you whether it's safe to embed as Factur-X/ZUGFeRD.
13
+ ```
14
+ $ trustrender validate examples/demo_stripe.json --source stripe
15
+
16
+ Invoice: INV-2026-0187
17
+ From:
18
+ To: Rheingold Maschinenbau GmbH
19
+ Items: 3
20
+ Total: $2,685.37
21
+
22
+ BLOCKED — 1 problem(s)
23
+
24
+ Missing vendor/sender name
25
+ Add a sender.name field to your invoice data.
26
+
27
+ This invoice cannot be processed until the problems above are fixed.
28
+ ```
15
29
 
16
30
  ```
17
- $ trustrender validate quickbooks_invoice.json
31
+ $ trustrender validate examples/demo_shopify.json --source shopify
32
+
33
+ Invoice: 1047
34
+ From:
35
+ To: Klaus Berger
36
+ Items: 3
37
+ Total: $1,309.00
38
+
39
+ BLOCKED — 1 problem(s)
40
+
41
+ Missing vendor/sender name
42
+ Add a sender.name field to your invoice data.
43
+
44
+ This invoice cannot be processed until the problems above are fixed.
45
+ ```
18
46
 
19
- Invoice: INV-2026-5541
20
- From: Summit Analytics Co.
21
- To: Horizon Financial
22
- Items: 2
23
- Total: $10,524.50
47
+ Add your seller identity to the source payload and it passes:
24
48
 
25
- Normalizations (25):
26
- DocNumber invoice_number CompanyName sender.name
27
- TxnDate → invoice_date customer.Name → recipient.name
28
- Line → items SubTotal → subtotal
29
- ... and 19 more
49
+ ```
50
+ $ trustrender validate examples/demo_stripe_ready.json --source stripe
51
+
52
+ Invoice: INV-2026-0187
53
+ From: NovaTech Solutions GmbH
54
+ To: Rheingold Maschinenbau GmbH
55
+ Items: 3
56
+ Total: $2,685.37
30
57
 
31
58
  PASS — invoice data is valid
32
59
 
33
60
  Safe to embed in Factur-X/ZUGFeRD PDF.
34
61
  ```
35
62
 
36
- Bad data gets blocked:
37
-
38
63
  ```
39
- $ trustrender validate ocr_extracted_invoice.json
64
+ $ trustrender validate examples/demo_shopify_ready.json --source shopify
40
65
 
41
- BLOCKED — 2 problem(s)
66
+ Invoice: 1047
67
+ From: Werkzeug-Kontor GmbH
68
+ To: Klaus Berger
69
+ Items: 3
70
+ Total: $1,309.00
42
71
 
43
- items[1] total is wrong
44
- You entered $459.00 but math says $450.00
45
- Fix the line total or the price/quantity.
72
+ PASS invoice data is valid
46
73
 
47
- Subtotal is wrong
48
- Lines add up to $12,459.00 but you listed $12,450.00
74
+ Safe to embed in Factur-X/ZUGFeRD PDF.
75
+ ```
49
76
 
50
- This invoice cannot be processed until the problems above are fixed.
77
+ The only difference between the blocked and passing files is one added field:
78
+
79
+ ```json
80
+ "sender": { "name": "NovaTech Solutions GmbH" }
51
81
  ```
52
82
 
83
+ ## Why this exists
84
+
85
+ Stripe and Shopify billing exports are missing seller fields, use platform-specific formats (cents, Unix timestamps, decimal strings), and have no concept of tax compliance. [factur-x](https://github.com/akretion/factur-x) and [drafthorse](https://github.com/pretix/python-drafthorse) generate compliant Factur-X/ZUGFeRD XML, but they assume clean input. TrustRender validates and normalizes source billing data before handoff — it catches arithmetic mismatches, missing required fields, and structural problems so they don't silently produce non-compliant documents.
86
+
53
87
  ## Install
54
88
 
55
89
  ```
@@ -71,20 +105,14 @@ Requires Python 3.11+.
71
105
 
72
106
  ```python
73
107
  from trustrender import validate_invoice
108
+ from trustrender.adapters import from_stripe
74
109
 
75
- result = validate_invoice({
76
- "invoiceNo": "INV-001",
77
- "vendor": {"companyName": "Acme Corp"},
78
- "customer": {"Name": "Client Inc"},
79
- "LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
80
- "SubTotal": 100,
81
- "tax": 8.50,
82
- "TotalAmt": 108.50,
83
- }, zugferd=True)
110
+ # Raw Stripe API response → validated canonical invoice
111
+ result = validate_invoice(from_stripe(raw_stripe_response), zugferd=True)
84
112
 
85
113
  if result["render_ready"] and result.get("zugferd_ready"):
86
- # safe to call factur-x / drafthorse
87
- print("All checks passed")
114
+ canonical = result["canonical"]
115
+ # safe to hand off to factur-x / drafthorse
88
116
  else:
89
117
  for error in result["errors"]:
90
118
  print(f"BLOCKED: {error['message']}")
@@ -99,6 +127,20 @@ else:
99
127
  - `normalizations`: field-level provenance (what was renamed, coerced, computed)
100
128
  - `zugferd_ready`: bool (if `zugferd=True`)
101
129
 
130
+ Also works with messy data from any source:
131
+
132
+ ```python
133
+ result = validate_invoice({
134
+ "invoiceNo": "INV-001",
135
+ "vendor": {"companyName": "Acme Corp"},
136
+ "customer": {"Name": "Client Inc"},
137
+ "LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
138
+ "SubTotal": 100,
139
+ "tax": 8.50,
140
+ "TotalAmt": 108.50,
141
+ }, zugferd=True)
142
+ ```
143
+
102
144
  ## Stripe adapter
103
145
 
104
146
  Raw Stripe Invoice API responses use cents, Unix timestamps, and nested structures. The adapter handles all of it:
@@ -108,13 +150,11 @@ trustrender validate stripe_invoice.json --source stripe --zugferd
108
150
  ```
109
151
 
110
152
  ```python
111
- from trustrender import validate_invoice
112
153
  from trustrender.adapters import from_stripe
113
-
114
154
  result = validate_invoice(from_stripe(raw_stripe_response), zugferd=True)
115
155
  ```
116
156
 
117
- The adapter converts cents to dollars, timestamps to dates, extracts line items from `lines.data[]`, and maps customer fields to recipient. Seller info is not included in Stripe invoices TrustRender will flag it if required for ZUGFeRD compliance.
157
+ Converts cents to dollars, timestamps to dates, extracts line items from `lines.data[]`, maps customer fields to recipient. If you enrich the source payload with `sender`, `vendor`, or `seller`, the adapter passes it through.
118
158
 
119
159
  ## Shopify adapter
120
160
 
@@ -125,13 +165,11 @@ trustrender validate shopify_order.json --source shopify
125
165
  ```
126
166
 
127
167
  ```python
128
- from trustrender import validate_invoice
129
168
  from trustrender.adapters import from_shopify
130
-
131
169
  result = validate_invoice(from_shopify(raw_shopify_order))
132
170
  ```
133
171
 
134
- The adapter parses string amounts to floats, combines first_name + last_name, maps order fields to invoice structure, and preserves structured address fields. Shopify orders have no seller info or due date — TrustRender handles both correctly.
172
+ Parses string amounts to floats, combines first_name + last_name, maps order fields to invoice structure, preserves structured address fields. Shopify orders have no seller info or due date — TrustRender flags the missing seller and handles the absent due date correctly.
135
173
 
136
174
  ## What it normalizes
137
175
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trustrender"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Validate and normalize Stripe, Shopify, and custom billing data for Factur-X/ZUGFeRD"
9
9
  license = "MIT"
10
10
  classifiers = [
@@ -36,7 +36,7 @@ class RenderResult:
36
36
  trace_id: str | None = None
37
37
 
38
38
 
39
- __version__ = "0.3.2"
39
+ __version__ = "0.3.3"
40
40
 
41
41
  __all__ = [
42
42
  # Core API (always available)
@@ -120,8 +120,15 @@ def from_shopify(raw: dict) -> dict:
120
120
  if recipient:
121
121
  out["recipient"] = recipient
122
122
 
123
- # Seller is NOT available in Shopify order objects.
124
- # The validation pipeline will flag this as blocked if sender.name is required.
123
+ # ── Seller passthrough ────────────────────────────────────────
124
+ # Shopify orders never include seller info. If the user has enriched
125
+ # the source payload with sender/vendor/seller metadata, preserve it.
126
+
127
+ if not isinstance(out.get("sender"), dict):
128
+ for key in ("sender", "vendor", "seller"):
129
+ if isinstance(raw.get(key), dict):
130
+ out["sender"] = raw[key]
131
+ break
125
132
 
126
133
  # ── Line items ───────────────────────────────────────────────
127
134
 
@@ -138,6 +138,16 @@ def from_stripe(raw: dict) -> dict:
138
138
  if items:
139
139
  out["items"] = items
140
140
 
141
+ # ── Seller passthrough ───────────────────────────────────────
142
+ # Stripe invoices never include seller info. If the user has enriched
143
+ # the source payload with sender/vendor/seller metadata, preserve it.
144
+
145
+ if not isinstance(out.get("sender"), dict):
146
+ for key in ("sender", "vendor", "seller"):
147
+ if isinstance(raw.get(key), dict):
148
+ out["sender"] = raw[key]
149
+ break
150
+
141
151
  # ── Metadata passthrough ─────────────────────────────────────
142
152
 
143
153
  if isinstance(raw.get("metadata"), dict) and raw["metadata"]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trustrender
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Validate and normalize Stripe, Shopify, and custom billing data for Factur-X/ZUGFeRD
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://trustrender.dev
@@ -42,56 +42,90 @@ Dynamic: license-file
42
42
 
43
43
  # TrustRender
44
44
 
45
- Validate and normalize billing data from Stripe, Shopify, and custom systems before Factur-X/ZUGFeRD embedding.
45
+ Validate and normalize billing data before Factur-X/ZUGFeRD embedding.
46
46
 
47
- If you're bridging a non-compliant billing platform into EU e-invoicing, TrustRender adds a validation and normalization layer before tools like [factur-x](https://github.com/akretion/factur-x) and [drafthorse](https://github.com/pretix/python-drafthorse) generate or embed compliant XML. It catches arithmetic mismatches, field misalignment, missing required fields, and structural problems before handoff.
47
+ ## Quick start
48
48
 
49
49
  ```
50
50
  pip install trustrender
51
- trustrender validate invoice.json
52
51
  ```
53
52
 
54
- ## What it does
53
+ Stripe and Shopify billing exports don't include the seller fields required for compliant invoices. TrustRender catches this:
55
54
 
56
- Takes invoice JSON from Stripe, Shopify, custom billing APIs, or legacy exports and tells you whether it's safe to embed as Factur-X/ZUGFeRD.
55
+ ```
56
+ $ trustrender validate examples/demo_stripe.json --source stripe
57
+
58
+ Invoice: INV-2026-0187
59
+ From:
60
+ To: Rheingold Maschinenbau GmbH
61
+ Items: 3
62
+ Total: $2,685.37
63
+
64
+ BLOCKED — 1 problem(s)
65
+
66
+ Missing vendor/sender name
67
+ Add a sender.name field to your invoice data.
68
+
69
+ This invoice cannot be processed until the problems above are fixed.
70
+ ```
57
71
 
58
72
  ```
59
- $ trustrender validate quickbooks_invoice.json
73
+ $ trustrender validate examples/demo_shopify.json --source shopify
74
+
75
+ Invoice: 1047
76
+ From:
77
+ To: Klaus Berger
78
+ Items: 3
79
+ Total: $1,309.00
80
+
81
+ BLOCKED — 1 problem(s)
82
+
83
+ Missing vendor/sender name
84
+ Add a sender.name field to your invoice data.
85
+
86
+ This invoice cannot be processed until the problems above are fixed.
87
+ ```
60
88
 
61
- Invoice: INV-2026-5541
62
- From: Summit Analytics Co.
63
- To: Horizon Financial
64
- Items: 2
65
- Total: $10,524.50
89
+ Add your seller identity to the source payload and it passes:
66
90
 
67
- Normalizations (25):
68
- DocNumber invoice_number CompanyName sender.name
69
- TxnDate → invoice_date customer.Name → recipient.name
70
- Line → items SubTotal → subtotal
71
- ... and 19 more
91
+ ```
92
+ $ trustrender validate examples/demo_stripe_ready.json --source stripe
93
+
94
+ Invoice: INV-2026-0187
95
+ From: NovaTech Solutions GmbH
96
+ To: Rheingold Maschinenbau GmbH
97
+ Items: 3
98
+ Total: $2,685.37
72
99
 
73
100
  PASS — invoice data is valid
74
101
 
75
102
  Safe to embed in Factur-X/ZUGFeRD PDF.
76
103
  ```
77
104
 
78
- Bad data gets blocked:
79
-
80
105
  ```
81
- $ trustrender validate ocr_extracted_invoice.json
106
+ $ trustrender validate examples/demo_shopify_ready.json --source shopify
82
107
 
83
- BLOCKED — 2 problem(s)
108
+ Invoice: 1047
109
+ From: Werkzeug-Kontor GmbH
110
+ To: Klaus Berger
111
+ Items: 3
112
+ Total: $1,309.00
84
113
 
85
- items[1] total is wrong
86
- You entered $459.00 but math says $450.00
87
- Fix the line total or the price/quantity.
114
+ PASS invoice data is valid
88
115
 
89
- Subtotal is wrong
90
- Lines add up to $12,459.00 but you listed $12,450.00
116
+ Safe to embed in Factur-X/ZUGFeRD PDF.
117
+ ```
91
118
 
92
- This invoice cannot be processed until the problems above are fixed.
119
+ The only difference between the blocked and passing files is one added field:
120
+
121
+ ```json
122
+ "sender": { "name": "NovaTech Solutions GmbH" }
93
123
  ```
94
124
 
125
+ ## Why this exists
126
+
127
+ Stripe and Shopify billing exports are missing seller fields, use platform-specific formats (cents, Unix timestamps, decimal strings), and have no concept of tax compliance. [factur-x](https://github.com/akretion/factur-x) and [drafthorse](https://github.com/pretix/python-drafthorse) generate compliant Factur-X/ZUGFeRD XML, but they assume clean input. TrustRender validates and normalizes source billing data before handoff — it catches arithmetic mismatches, missing required fields, and structural problems so they don't silently produce non-compliant documents.
128
+
95
129
  ## Install
96
130
 
97
131
  ```
@@ -113,20 +147,14 @@ Requires Python 3.11+.
113
147
 
114
148
  ```python
115
149
  from trustrender import validate_invoice
150
+ from trustrender.adapters import from_stripe
116
151
 
117
- result = validate_invoice({
118
- "invoiceNo": "INV-001",
119
- "vendor": {"companyName": "Acme Corp"},
120
- "customer": {"Name": "Client Inc"},
121
- "LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
122
- "SubTotal": 100,
123
- "tax": 8.50,
124
- "TotalAmt": 108.50,
125
- }, zugferd=True)
152
+ # Raw Stripe API response → validated canonical invoice
153
+ result = validate_invoice(from_stripe(raw_stripe_response), zugferd=True)
126
154
 
127
155
  if result["render_ready"] and result.get("zugferd_ready"):
128
- # safe to call factur-x / drafthorse
129
- print("All checks passed")
156
+ canonical = result["canonical"]
157
+ # safe to hand off to factur-x / drafthorse
130
158
  else:
131
159
  for error in result["errors"]:
132
160
  print(f"BLOCKED: {error['message']}")
@@ -141,6 +169,20 @@ else:
141
169
  - `normalizations`: field-level provenance (what was renamed, coerced, computed)
142
170
  - `zugferd_ready`: bool (if `zugferd=True`)
143
171
 
172
+ Also works with messy data from any source:
173
+
174
+ ```python
175
+ result = validate_invoice({
176
+ "invoiceNo": "INV-001",
177
+ "vendor": {"companyName": "Acme Corp"},
178
+ "customer": {"Name": "Client Inc"},
179
+ "LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
180
+ "SubTotal": 100,
181
+ "tax": 8.50,
182
+ "TotalAmt": 108.50,
183
+ }, zugferd=True)
184
+ ```
185
+
144
186
  ## Stripe adapter
145
187
 
146
188
  Raw Stripe Invoice API responses use cents, Unix timestamps, and nested structures. The adapter handles all of it:
@@ -150,13 +192,11 @@ trustrender validate stripe_invoice.json --source stripe --zugferd
150
192
  ```
151
193
 
152
194
  ```python
153
- from trustrender import validate_invoice
154
195
  from trustrender.adapters import from_stripe
155
-
156
196
  result = validate_invoice(from_stripe(raw_stripe_response), zugferd=True)
157
197
  ```
158
198
 
159
- The adapter converts cents to dollars, timestamps to dates, extracts line items from `lines.data[]`, and maps customer fields to recipient. Seller info is not included in Stripe invoices TrustRender will flag it if required for ZUGFeRD compliance.
199
+ Converts cents to dollars, timestamps to dates, extracts line items from `lines.data[]`, maps customer fields to recipient. If you enrich the source payload with `sender`, `vendor`, or `seller`, the adapter passes it through.
160
200
 
161
201
  ## Shopify adapter
162
202
 
@@ -167,13 +207,11 @@ trustrender validate shopify_order.json --source shopify
167
207
  ```
168
208
 
169
209
  ```python
170
- from trustrender import validate_invoice
171
210
  from trustrender.adapters import from_shopify
172
-
173
211
  result = validate_invoice(from_shopify(raw_shopify_order))
174
212
  ```
175
213
 
176
- The adapter parses string amounts to floats, combines first_name + last_name, maps order fields to invoice structure, and preserves structured address fields. Shopify orders have no seller info or due date — TrustRender handles both correctly.
214
+ Parses string amounts to floats, combines first_name + last_name, maps order fields to invoice structure, preserves structured address fields. Shopify orders have no seller info or due date — TrustRender flags the missing seller and handles the absent due date correctly.
177
215
 
178
216
  ## What it normalizes
179
217
 
@@ -100,6 +100,35 @@ class TestStripeAdapter:
100
100
  result = from_stripe({"number": "INV-001", "customer_name": "Buyer"})
101
101
  assert "sender" not in result
102
102
 
103
+ def test_sender_passthrough(self):
104
+ """If user enriches source with sender dict, adapter preserves it."""
105
+ result = from_stripe({"number": "INV-001", "sender": {"name": "Acme GmbH"}})
106
+ assert result["sender"] == {"name": "Acme GmbH"}
107
+
108
+ def test_vendor_passthrough_as_sender(self):
109
+ """vendor dict maps to sender if sender absent."""
110
+ result = from_stripe({"number": "INV-001", "vendor": {"name": "Acme GmbH"}})
111
+ assert result["sender"] == {"name": "Acme GmbH"}
112
+
113
+ def test_seller_passthrough_as_sender(self):
114
+ """seller dict maps to sender if sender and vendor absent."""
115
+ result = from_stripe({"number": "INV-001", "seller": {"name": "Acme GmbH"}})
116
+ assert result["sender"] == {"name": "Acme GmbH"}
117
+
118
+ def test_sender_takes_priority_over_vendor(self):
119
+ """First valid dict wins: sender > vendor > seller."""
120
+ result = from_stripe({
121
+ "number": "INV-001",
122
+ "sender": {"name": "Sender"},
123
+ "vendor": {"name": "Vendor"},
124
+ })
125
+ assert result["sender"]["name"] == "Sender"
126
+
127
+ def test_non_dict_sender_ignored(self):
128
+ """String sender is not a valid enrichment — ignored."""
129
+ result = from_stripe({"number": "INV-001", "sender": "Acme GmbH"})
130
+ assert "sender" not in result
131
+
103
132
  def test_missing_fields_left_missing(self):
104
133
  """Adapter does not guess. Missing data stays missing."""
105
134
  result = from_stripe({})
@@ -284,6 +313,18 @@ class TestShopifyAdapter:
284
313
  result = from_shopify({"name": "#1", "customer": {"first_name": "A", "last_name": "B"}})
285
314
  assert "sender" not in result
286
315
 
316
+ def test_sender_passthrough(self):
317
+ result = from_shopify({"name": "#1", "sender": {"name": "Werkzeug GmbH"}})
318
+ assert result["sender"] == {"name": "Werkzeug GmbH"}
319
+
320
+ def test_vendor_passthrough_as_sender(self):
321
+ result = from_shopify({"name": "#1", "vendor": {"name": "Werkzeug GmbH"}})
322
+ assert result["sender"] == {"name": "Werkzeug GmbH"}
323
+
324
+ def test_non_dict_sender_ignored(self):
325
+ result = from_shopify({"name": "#1", "sender": "Werkzeug GmbH"})
326
+ assert "sender" not in result
327
+
287
328
  def test_missing_fields_left_missing(self):
288
329
  result = from_shopify({})
289
330
  assert "invoice_number" not in result
File without changes
File without changes