| package config |
|
|
| import ( |
| "errors" |
| "fmt" |
| "strings" |
|
|
| "github.com/spf13/viper" |
|
|
| "github.com/openmeterio/openmeter/openmeter/taxcode" |
| ) |
|
|
| type TaxCodeConfiguration struct { |
| Seeds []TaxCodeSeed `mapstructure:"seeds"` |
| } |
|
|
| |
| |
| |
| type TaxCodeSeed struct { |
| Key string `mapstructure:"key"` |
| Name string `mapstructure:"name"` |
| Description *string `mapstructure:"description"` |
| AppMappings []TaxCodeAppMapping `mapstructure:"appMappings"` |
| DefaultInvoicing bool `mapstructure:"defaultInvoicing"` |
| DefaultCreditGrant bool `mapstructure:"defaultCreditGrant"` |
| } |
|
|
| type TaxCodeAppMapping struct { |
| AppType string `mapstructure:"appType"` |
| TaxCode string `mapstructure:"taxCode"` |
| } |
|
|
| func (c TaxCodeConfiguration) Validate() error { |
| var errs []error |
|
|
| if len(c.Seeds) == 0 { |
| errs = append(errs, errors.New("seeds must not be empty")) |
| return errors.Join(errs...) |
| } |
|
|
| keys := make(map[string]struct{}, len(c.Seeds)) |
| defaultInvoicingCount := 0 |
| defaultCreditGrantCount := 0 |
|
|
| for i, seed := range c.Seeds { |
| trimmedKey := strings.TrimSpace(seed.Key) |
| if trimmedKey == "" { |
| errs = append(errs, fmt.Errorf("seed[%d]: key must not be empty", i)) |
| } |
|
|
| if strings.TrimSpace(seed.Name) == "" { |
| errs = append(errs, fmt.Errorf("seed[%d]: name must not be empty", i)) |
| } |
|
|
| if trimmedKey != "" { |
| if _, exists := keys[trimmedKey]; exists { |
| errs = append(errs, fmt.Errorf("seed[%d]: duplicate key %q", i, seed.Key)) |
| } |
| keys[trimmedKey] = struct{}{} |
| } |
|
|
| if seed.DefaultInvoicing { |
| defaultInvoicingCount++ |
| } |
|
|
| if seed.DefaultCreditGrant { |
| defaultCreditGrantCount++ |
| } |
|
|
| for j, mapping := range seed.AppMappings { |
| trimmedAppType := strings.TrimSpace(mapping.AppType) |
| trimmedTaxCode := strings.TrimSpace(mapping.TaxCode) |
|
|
| if trimmedAppType == "" { |
| errs = append(errs, fmt.Errorf("seed[%d].appMappings[%d]: appType must not be empty", i, j)) |
| } |
|
|
| if trimmedTaxCode == "" { |
| errs = append(errs, fmt.Errorf("seed[%d].appMappings[%d]: taxCode must not be empty", i, j)) |
| } else if trimmedAppType == "stripe" && !taxcode.TaxCodeStripeRegexp.MatchString(trimmedTaxCode) { |
| errs = append(errs, fmt.Errorf("seed[%d].appMappings[%d]: taxCode %q is not a valid Stripe tax code (must match %s)", i, j, trimmedTaxCode, taxcode.TaxCodeStripeRegexp.String())) |
| } |
| } |
| } |
|
|
| if defaultInvoicingCount != 1 { |
| errs = append(errs, fmt.Errorf("exactly one seed must have defaultInvoicing=true, got %d", defaultInvoicingCount)) |
| } |
|
|
| if defaultCreditGrantCount != 1 { |
| errs = append(errs, fmt.Errorf("exactly one seed must have defaultCreditGrant=true, got %d", defaultCreditGrantCount)) |
| } |
|
|
| return errors.Join(errs...) |
| } |
|
|
| func ConfigureTaxCode(v *viper.Viper) { |
| v.SetDefault("taxcode.seeds", []map[string]any{ |
| { |
| "key": taxcode.ProviderDefaultTaxCodeKey, |
| "name": "Provider default", |
| "defaultInvoicing": true, |
| }, |
| { |
| "key": "nontaxable", |
| "name": "Nontaxable", |
| "defaultCreditGrant": true, |
| "appMappings": []map[string]any{ |
| { |
| "appType": "stripe", |
| "taxCode": "txcd_00000000", |
| }, |
| }, |
| }, |
| }) |
| } |
|
|