Skip to content

Route us_ein tax IDs to org.Identity - #45

Merged
pmenendz merged 1 commit into
mainfrom
us-ein-org-identity
Jun 30, 2026
Merged

Route us_ein tax IDs to org.Identity#45
pmenendz merged 1 commit into
mainfrom
us-ein-org-identity

Conversation

@pmenendz

Copy link
Copy Markdown
Contributor

Problem

Converting a Stripe invoice for a US customer with a us_ein tax ID failed when creating the silo entry:

fatal: creating silo entry: 422: :(customer > tax_id > code: tax id code must have a valid format)

us_ein values were being mapped into a GOBL tax.Identity, whose code must match ^[A-Z0-9]+$. GOBL would normally strip separators during normalization, but only when no tax regime exists for the country. A US regime does exist and defines no normalizer, so the hyphen in EIN values (e.g. 41-4637166) was left in place and validation failed with code: must be in a valid format.

There's also a conceptual issue: GOBL's US regime states US entities "do not have an official tax scheme and should omit the code field," and defines us.IdentityTypeEIN as an org.Identity type — so an EIN should live in the party's identities array, not in tax_id.code.

Fix

Route us_ein to a GOBL org.Identity (type EIN) instead of a tax.Identity, mirroring how the German Steuernummer (de_stn) is already handled. org.Identity codes permit hyphens as separators, so 41-4637166 validates as-is.

  • Add us_ein to orgIDKeys so it's recognized as a party identity in all conversion paths (customer, supplier-from-invoice, supplier-from-account).
  • FromTaxIDToOrg (Stripe → GOBL): map us_einorg.Identity{Type: EIN, ...}.
  • ToTaxIDFromOrg (GOBL → Stripe): symmetric reverse mapping.
  • Both org-identity mappings (de_stn and us_ein) now also set the identity's country, making them self-describing.

Resulting customer representation:

"identities": [
  { "country": "US", "type": "EIN", "code": "41-4637166" }
]

Verification

  • End-to-end conversion of the failing invoice now passes envelope validation (previously: customer: (tax_id: (code: must be in a valid format.))).
  • Added a us_ein case to TestFromTaxIDToOrg and updated the de_stn assertions for the new country field.
  • Full suite passes (go test ./...), go vet and gofmt clean.

🤖 Generated with Claude Code

US Employer Identification Numbers (us_ein) were being mapped to a GOBL
tax.Identity, whose code must match ^[A-Z0-9]+$. Because the US regime
defines no normalizer, the hyphen in EIN values (e.g. "41-4637166") was
left in place and validation failed with "code: must be in a valid
format", surfacing as a 422 when creating the silo entry.

US EINs don't belong to a VAT-like tax scheme, so they should be
represented as a party org.Identity (type EIN) rather than a tax.Identity
code, matching how the German Steuernummer (de_stn) is already handled.
org.Identity codes permit hyphens as separators, so the value validates.

Both org-identity mappings now also set the identity's country to make
them self-describing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 30, 2026 09:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Stripe invoice conversion failures for US customers with us_ein tax IDs by mapping EINs to GOBL party identities (org.Identity) instead of tax identities (tax.Identity), aligning with GOBL’s US regime model and avoiding validation errors caused by hyphenated EIN formats.

Changes:

  • Treat Stripe us_ein as an org.Identity (type EIN) rather than a tax.Identity, and include it in the identity-routing logic (orgIDKeys).
  • Add symmetric org-identity mapping support for us_ein in both conversion directions, and set Country on org identities (de_stn, us_ein).
  • Update unit tests to cover the new FromTaxIDToOrg behavior and updated identity expectations with Country.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
party.go Routes us_ein through org.Identity conversions (incl. country) and adds reverse mapping support in ToTaxIDFromOrg.
party_test.go Updates identity assertions to include Country and adds a new us_ein case for FromTaxIDToOrg.
Comments suppressed due to low confidence (1)

party.go:223

  • FromTaxIDToOrg can leave oid as nil for unsupported Stripe tax ID types, but it still calls oid.Normalize() unconditionally. Even if Normalize currently tolerates a nil receiver, an explicit nil check here would make the control flow clearer and avoid relying on upstream nil-safety.
			Code:    cbc.Code(taxID.Value),
		}
	}
	oid.Normalize()
	return oid

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread party_test.go
@pmenendz
pmenendz merged commit e2f05af into main Jun 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants