Skip to content

Commit 1603ab5

Browse files
committed
fix(taxcode): reject soft-deleted tax codes as organization defaults
1 parent 296456e commit 1603ab5

2 files changed

Lines changed: 60 additions & 7 deletions

File tree

openmeter/taxcode/service/organizationdefaulttaxcodes.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,34 @@ func (s *Service) UpsertOrganizationDefaultTaxCodes(ctx context.Context, input t
2424
}
2525

2626
return transaction.Run(ctx, s.adapter, func(ctx context.Context) (taxcode.OrganizationDefaultTaxCodes, error) {
27-
// Ensure both tax code IDs belong to the namespace.
28-
if _, err := s.GetTaxCode(ctx, taxcode.GetTaxCodeInput{
29-
NamespacedID: models.NamespacedID{Namespace: input.Namespace, ID: input.InvoicingTaxCodeID},
30-
}); err != nil {
27+
// Ensure both tax code IDs belong to the namespace and are not soft-deleted.
28+
if err := s.requireActiveTaxCode(ctx, input.Namespace, input.InvoicingTaxCodeID); err != nil {
3129
return taxcode.OrganizationDefaultTaxCodes{}, err
3230
}
3331

34-
if _, err := s.GetTaxCode(ctx, taxcode.GetTaxCodeInput{
35-
NamespacedID: models.NamespacedID{Namespace: input.Namespace, ID: input.CreditGrantTaxCodeID},
36-
}); err != nil {
32+
if err := s.requireActiveTaxCode(ctx, input.Namespace, input.CreditGrantTaxCodeID); err != nil {
3733
return taxcode.OrganizationDefaultTaxCodes{}, err
3834
}
3935

4036
return s.adapter.UpsertOrganizationDefaultTaxCodes(ctx, input)
4137
})
4238
}
39+
40+
// requireActiveTaxCode ensures the tax code belongs to the namespace and is not soft-deleted.
41+
// GetTaxCode returns soft-deleted rows by ID (so billing can still resolve frozen Stripe
42+
// mappings), so a deleted code must be rejected explicitly to prevent it from being
43+
// designated as an organization default.
44+
func (s *Service) requireActiveTaxCode(ctx context.Context, namespace, id string) error {
45+
tc, err := s.GetTaxCode(ctx, taxcode.GetTaxCodeInput{
46+
NamespacedID: models.NamespacedID{Namespace: namespace, ID: id},
47+
})
48+
if err != nil {
49+
return err
50+
}
51+
52+
if tc.DeletedAt != nil {
53+
return taxcode.NewTaxCodeNotFoundError(id)
54+
}
55+
56+
return nil
57+
}

openmeter/taxcode/service/organizationdefaulttaxcodes_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,44 @@ func TestOrganizationDefaultTaxCodesService(t *testing.T) {
117117
assert.True(t, taxcode.IsTaxCodeNotFoundError(err), "tax code from another namespace must not resolve")
118118
})
119119

120+
t.Run("SoftDeleted/InvoicingTaxCodeIsDeleted", func(t *testing.T) {
121+
dns := testutils.NameGenerator.Generate().Key
122+
env.SetupNamespaceDefaults(t, dns)
123+
deleted := env.CreateTaxCode(t, dns)
124+
active := env.CreateTaxCode(t, dns)
125+
126+
require.NoError(t, env.Service.DeleteTaxCode(t.Context(), taxcode.DeleteTaxCodeInput{
127+
NamespacedID: models.NamespacedID{Namespace: dns, ID: deleted.ID},
128+
}))
129+
130+
_, err := env.Service.UpsertOrganizationDefaultTaxCodes(t.Context(), taxcode.UpsertOrganizationDefaultTaxCodesInput{
131+
Namespace: dns,
132+
InvoicingTaxCodeID: deleted.ID,
133+
CreditGrantTaxCodeID: active.ID,
134+
})
135+
require.Error(t, err)
136+
assert.True(t, taxcode.IsTaxCodeNotFoundError(err), "soft-deleted tax code must not be settable as an organization default")
137+
})
138+
139+
t.Run("SoftDeleted/CreditGrantTaxCodeIsDeleted", func(t *testing.T) {
140+
dns := testutils.NameGenerator.Generate().Key
141+
env.SetupNamespaceDefaults(t, dns)
142+
active := env.CreateTaxCode(t, dns)
143+
deleted := env.CreateTaxCode(t, dns)
144+
145+
require.NoError(t, env.Service.DeleteTaxCode(t.Context(), taxcode.DeleteTaxCodeInput{
146+
NamespacedID: models.NamespacedID{Namespace: dns, ID: deleted.ID},
147+
}))
148+
149+
_, err := env.Service.UpsertOrganizationDefaultTaxCodes(t.Context(), taxcode.UpsertOrganizationDefaultTaxCodesInput{
150+
Namespace: dns,
151+
InvoicingTaxCodeID: active.ID,
152+
CreditGrantTaxCodeID: deleted.ID,
153+
})
154+
require.Error(t, err)
155+
assert.True(t, taxcode.IsTaxCodeNotFoundError(err), "soft-deleted tax code must not be settable as an organization default")
156+
})
157+
120158
t.Run("Create", func(t *testing.T) {
121159
ns2 := testutils.NameGenerator.Generate().Key
122160
invoicing := env.CreateTaxCode(t, ns2)

0 commit comments

Comments
 (0)