Zum Inhalt springen

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.

After invoice finalization, the payment workflow stores the PDF in object storage and invoice metadata in D1. A scheduled read-only audit checks retention integrity and reports missing documents without automatically deleting invoices.After invoice finalization, the payment workflow stores the PDF in object storage and invoice metadata in D1. A scheduled read-only audit checks retention integrity and reports missing documents without automatically deleting invoices.

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.

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.

An account deletion request first checks unpaid invoices. When deletion is allowed, the service retains justified fraud-prevention hashes, anonymizes the user and clears sessions. Required invoice records remain, and the user receives confirmation.An account deletion request first checks unpaid invoices. When deletion is allowed, the service retains justified fraud-prevention hashes, anonymizes the user and clears sessions. Required invoice records remain, and the user receives confirmation.

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.”

  • anonymizeUser is idempotent: running twice on an already-anonymised user is a no-op (guarded by anonymized_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.update zeroes email + name to a hash; metadata field anonymized=true is set for audit.