skip to content
← back to all projects
shipped github ↗

stripe-eu-vat-moss

EU VAT One-Stop-Shop automation engine.

Java 21 Spring Boot JOOQ Postgres Pulumi

stripe-eu-vat-moss

EU One-Stop-Shop · destination-rate VAT routing per Art. 58

shipped
customer in27 member states · live rates
net€100.00
vat 19.0% DE treasury+ €19.00
customer pays€119.00
period 2026-Q2saf-oss ready

Elevator pitch

Spring Boot service that turns Stripe tax + Connect data into a compliant quarterly EU OSS VAT return. Bitemporal event store, 27-country VAT matrix, SAF-OSS XML output, Pulumi-Java on Oracle Cloud Free.

What it is

Java 21 + Spring Boot 3.4 + Spring Modulith. Eight modules:

  • moss-shared — primitive value types (Money, Country, Period, IsoCurrency, UUIDv7), jqwik property tests
  • moss-ledger — bitemporal Postgres event store via JOOQ (no JPA), append-only with effective + recorded time
  • moss-enrich — 27-country VAT rate matrix as Liquibase data, ECB FX feed parser, VIES SOAP client with 24h cache, place-of-supply resolver
  • moss-ingest — Stripe itemized tax CSV parser, resumable cursor, idempotent ingest, webhook handler with HMAC-SHA256 verification, Connect deemed-seller routing (Art. 14a)
  • moss-file — SAF-OSS JAXB marshaller with optional XSD validation, sha256 sealing, grouping rules per OSS Guidelines §3.6
  • moss-observe — Micrometer dual registry (Prometheus + OTLP), Grafana dashboard JSON
  • moss-api — REST controllers + OpenAPI spec
  • moss-cli — Picocli: ingest-csv, close-period, generate-return, audit-replay

Why this exists

Every marketplace using Stripe Connect across the EU has to file an OSS VAT return quarterly. Stripe ingests the tax data; what they don’t do is generate the per-member-state report in the SAF-OSS XML format each tax authority requires. Most teams hand-roll this in Python notebooks and pray. This is the boring, correct version.

Architecture

stripe webhooks   ECB feed   VIES SOAP
       │             │           │
       ▼             ▼           ▼
   ┌──────────────────────────────────┐
   │   moss-ingest    moss-enrich     │
   └────────────────┬─────────────────┘

        ┌──────────────────────┐
        │ moss-ledger (bitemp) │  ← postgres event store
        └──────────┬───────────┘

        ┌──────────────────────┐
        │   moss-file (XML)    │  → saf-oss return + sha256
        └──────────────────────┘

       ┌───────────┴───────────┐
       ▼                       ▼
   moss-api               moss-cli

The event store is bitemporal — every fact has effective_at (when the sale happened) AND recorded_at (when we learned about it). Lets us replay a return as we knew it on date X, which is what auditors actually ask for.

Key decisions

  • JOOQ, not JPA — bitemporal queries need explicit SQL. Hibernate fights you here. JOOQ generates type-safe DSL from the schema.
  • Spring Modulith over microservices — single-deployable, module-boundary enforcement via ArchUnit. No service mesh for a single-team OSS project.
  • Liquibase, not Flyway — XML changesets + the ability to express the 27-country rate matrix as data, not migrations.
  • Picocli CLI as first-class — every API operation is also a CLI subcommand. Auditors don’t want a UI; they want a deterministic command they can re-run.
  • Pulumi-Java on Oracle Cloud Always-Free — €0 hosting. ARM Ampere A1 + autonomous Postgres + 10 GB object storage.
  • Pitest + jqwik — mutation testing (72% kill rate on compliance surfaces) + property testing for rounding/threshold edges. The hard part of tax software is the math edge cases.

Numbers worth knowing

Lines of Java5,433
Tests116 (unit + IT + property), 100% pass
Pitest mutation score72% (test strength 83%)
Modules9 (eight moss-* + infra)
Liquibase changesets27-country rate matrix + 2026 FI/LT VAT changes
ContainerJib + distroless java21, 260 MB linux/amd64
Deploy cost€0 on Oracle Cloud Always-Free
Releasev0.1.0 tagged + CycloneDX SBOM

Status

Repogithub.com/mateokadiu/stripe-eu-vat-moss
LicenseMIT
Build./gradlew check assemble — green
Container./gradlew jibDockerBuild — distroless 260 MB
Deploypulumi up in infra/ — Oracle Cloud Always-Free