Process 12 — invoice retention (D29 dual-write) & DSGVO erasure (Art. 17 vs ban-list)
Process 12 — invoice retention (D29 dual-write) & DSGVO erasure (Art. 17 vs ban-list)
Section titled “Process 12 — invoice retention (D29 dual-write) & DSGVO erasure (Art. 17 vs ban-list)”Two interleaved flows owned by 12-legal-compliance. Both are triggered independently and may run concurrently against the same user record without conflicts: invoice retention is append-only / immutable; DSGVO erasure anonymises users while preserving ban_list under Art. 6(1)(f).
Flow A — Invoice retention (10 years, §147 AO / §14b UStG)
Section titled “Flow A — Invoice retention (10 years, §147 AO / §14b UStG)”Saga 05 produces a finalized Stripe invoice. The dual-write (master D29) writes the PDF to R2 and pins the Stripe-hosted URL in D1, so either side is sufficient legal evidence.
Recovery from R2 loss
Section titled “Recovery from R2 loss”If R2 ever loses an object (404 on the dual-write audit), the D1 hosted_invoice_url is the canonical fallback. Stripe retains finalized invoices for ≥ 10 years per ToS. Admin runbook re-fetches from hosted_invoice_url, re-PUTs to R2, and emits an incident report.
Credit notes
Section titled “Credit notes”Identical pattern, but Stripe call is creditNotes.create and R2 prefix is credit-notes/. Same dual-write; same retention; same audit.
Flow B — DSGVO erasure (Art. 17) preserving ban-list (Art. 6(1)(f))
Section titled “Flow B — DSGVO erasure (Art. 17) preserving ban-list (Art. 6(1)(f))”User requests deletion via /api/me/delete-account (08-advertiser-dashboard UI). Backend dispatches to 02-identity-onboarding.anonymizeUser(), with 12-legal-compliance enforcing the ban-list preservation rule.
Re-registration block
Section titled “Re-registration block”Future POST /register in 02 checks ban_list by sha256(vat_id+pepper). Hit → legal.banned-entity with admin-appeal link. Admin (10) can manually clear a ban-list row; action audited in user_admin_actions.
Art. 14 transparency note in confirmation email
Section titled “Art. 14 transparency note in confirmation email”When ban_list_preserved=true, the mail includes:
“Aufgrund §-Verstöße auf der Plattform wurde ein gehashter Datensatz (hashed_vat_id, hashed_email, strikes, banned_at) auf Basis unseres berechtigten Interesses (Art. 6(1)(f) DSGVO) zur Betrugsabwehr aufbewahrt. Dieser Datensatz enthält keine direkt identifizierenden Personendaten. Widerspruch nach Art. 21 DSGVO ist möglich; bitte wenden Sie sich an datenschutz@nowyourlink.com.”
Invariants
Section titled “Invariants”anonymizeUseris idempotent: running twice on an already-anonymised user is a no-op (guarded byanonymized_at IS NOT NULL).- The ban-list write happens before the anonymise UPDATE, so a crash mid-flow never loses the strike record.
- All steps run inside a single D1 batch / transaction where possible; the KV deletes are best-effort with retry queue fallback.
- The Stripe Customer record is not deleted (Stripe needs it to retain invoices for their 10y obligation).
customers.updatezeroesemail+nameto a hash; metadata fieldanonymized=trueis set for audit.