Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ func main() {
os.Exit(1)
}

// Provision default tax codes
err = app.TaxCodeService.ProvisionDefaultTaxCodes(ctx, app.NamespaceManager.GetDefaultNamespace())
if err != nil {
logger.Error("failed to provision default tax codes", "error", err)
os.Exit(1)
}

// Create meters from config
err = app.MeterConfigInitializer(ctx)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions openmeter/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1904,6 +1904,10 @@ func (n NoopTaxCodeService) GetOrCreateByAppMapping(ctx context.Context, input t
return taxcode.TaxCode{}, nil
}

func (n NoopTaxCodeService) ProvisionDefaultTaxCodes(ctx context.Context, namespace string) error {
return nil
}

// SubjectService methods

var _ subject.Service = &NoopSubjectService{}
Expand Down
57 changes: 57 additions & 0 deletions openmeter/taxcode/defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package taxcode

import "github.com/samber/lo"

// DefaultTaxCodeSeed holds the minimal inputs needed to seed one well-known tax code.
type DefaultTaxCodeSeed struct {
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.

Can't we reuse the CreateTaxCodeInput type?

Key string
Name string
Description *string
StripeCode string
}

// DefaultStripeTaxCodes is a curated list of commonly used Stripe tax codes for
// software and SaaS businesses. These are seeded at startup so users can browse
// and select from known codes without having to look up Stripe-specific identifiers.
//
// Keys follow the same convention used by GetOrCreateByAppMapping: "stripe_<code>".
// This ensures that if a code was already auto-created by the billing flow, the
// seed CreateTaxCode call will hit the unique constraint and be silently skipped.
var DefaultStripeTaxCodes = []DefaultTaxCodeSeed{
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.

These should come from config.

{
Key: "stripe_txcd_00000000",
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.

I'd choose a key that's specific to our platform. When we support other providers, the same tax code will map between providers.

Suggested change
Key: "stripe_txcd_00000000",
Key: "non_taxable",

or we can define a format for OM

Suggested change
Key: "stripe_txcd_00000000",
Key: "om_......",

Name: "Non-taxable",
Description: lo.ToPtr("Use for products or services that are explicitly exempt from tax."),
StripeCode: "txcd_00000000",
},
{
Key: "stripe_txcd_10103001",
Name: "SaaS - Business Use",
Description: lo.ToPtr("Software as a Service intended for business use."),
StripeCode: "txcd_10103001",
},
{
Key: "stripe_txcd_10101000",
Name: "Infrastructure as a Service (IaaS)",
Description: lo.ToPtr("Cloud infrastructure services such as compute, storage, and networking."),
StripeCode: "txcd_10101000",
},
{
Key: "stripe_txcd_10102000",
Name: "Platform as a Service (PaaS)",
Description: lo.ToPtr("Cloud platform services providing a managed environment for building and deploying applications."),
StripeCode: "txcd_10102000",
},
{
Key: "stripe_txcd_10000000",
Name: "Digital Goods - General",
Description: lo.ToPtr("General category for digital goods and electronically supplied services. Use when no more specific code applies."),
StripeCode: "txcd_10000000",
},
{
Key: "stripe_txcd_20030000",
Name: "Professional Services",
Description: lo.ToPtr("Consulting, advisory, and other professional services."),
StripeCode: "txcd_20030000",
},
}
5 changes: 5 additions & 0 deletions openmeter/taxcode/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ type Service interface {
GetTaxCodeByAppMapping(ctx context.Context, input GetTaxCodeByAppMappingInput) (TaxCode, error)
GetOrCreateByAppMapping(ctx context.Context, input GetOrCreateByAppMappingInput) (TaxCode, error)
DeleteTaxCode(ctx context.Context, input DeleteTaxCodeInput) error

// ProvisionDefaultTaxCodes seeds the well-known Stripe tax codes defined in
// DefaultStripeTaxCodes into the given namespace. The operation is idempotent:
// codes that already exist (matched by key) are silently skipped.
ProvisionDefaultTaxCodes(ctx context.Context, namespace string) error
}

var (
Expand Down
26 changes: 26 additions & 0 deletions openmeter/taxcode/service/taxcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log/slog"

"github.com/openmeterio/openmeter/openmeter/app"
"github.com/openmeterio/openmeter/openmeter/taxcode"
"github.com/openmeterio/openmeter/pkg/framework/transaction"
"github.com/openmeterio/openmeter/pkg/models"
Expand Down Expand Up @@ -124,3 +125,28 @@ func (s *service) DeleteTaxCode(ctx context.Context, input taxcode.DeleteTaxCode
return s.adapter.DeleteTaxCode(ctx, input)
})
}

// ProvisionDefaultTaxCodes seeds the well-known Stripe tax codes from
// taxcode.DefaultStripeTaxCodes into the given namespace inside a single
// transaction. Already-existing codes (matched by key) are silently skipped,
// making the call idempotent.
func (s *service) ProvisionDefaultTaxCodes(ctx context.Context, namespace string) error {
return transaction.RunWithNoValue(ctx, s.adapter, func(ctx context.Context) error {
for _, seed := range taxcode.DefaultStripeTaxCodes {
_, err := s.adapter.CreateTaxCode(ctx, taxcode.CreateTaxCodeInput{
Namespace: namespace,
Key: seed.Key,
Name: seed.Name,
Description: seed.Description,
AppMappings: taxcode.TaxCodeAppMappings{
{AppType: app.AppTypeStripe, TaxCode: seed.StripeCode},
},
})
if err != nil && !models.IsGenericConflictError(err) {
return fmt.Errorf("failed to provision default tax code %q: %w", seed.Key, err)
}
}

return nil
})
}
Loading