mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-31 05:07:09 +00:00
133 lines
3.7 KiB
Go
133 lines
3.7 KiB
Go
//go:build billing
|
|
|
|
package billing
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
// BillingConfig holds billing/Stripe settings parsed from the hold config YAML.
|
|
// The billing section is a top-level key in the YAML file, separate from quota.
|
|
type BillingConfig struct {
|
|
Enabled bool
|
|
Currency string
|
|
SuccessURL string
|
|
CancelURL string
|
|
|
|
// Tier-level billing info keyed by tier name (same keys as quota tiers).
|
|
Tiers map[string]BillingTierConfig
|
|
|
|
// Tier assigned to plankowner crew members.
|
|
PlankOwnerCrewTier string
|
|
}
|
|
|
|
// BillingTierConfig holds Stripe pricing for a single tier.
|
|
type BillingTierConfig struct {
|
|
Description string `yaml:"description,omitempty"`
|
|
StripePriceMonthly string `yaml:"stripe_price_monthly,omitempty"`
|
|
StripePriceYearly string `yaml:"stripe_price_yearly,omitempty"`
|
|
}
|
|
|
|
// billingYAML is the top-level YAML structure for extracting the billing section.
|
|
type billingYAML struct {
|
|
Billing rawBillingConfig `yaml:"billing"`
|
|
}
|
|
|
|
type rawBillingConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Currency string `yaml:"currency,omitempty"`
|
|
SuccessURL string `yaml:"success_url,omitempty"`
|
|
CancelURL string `yaml:"cancel_url,omitempty"`
|
|
PlankOwnerCrewTier string `yaml:"plankowner_crew_tier,omitempty"`
|
|
Tiers map[string]BillingTierConfig `yaml:"tiers,omitempty"`
|
|
}
|
|
|
|
// LoadBillingConfig reads the hold config YAML and extracts billing fields.
|
|
// Returns (nil, nil) if the file is missing or billing is not enabled.
|
|
// Returns (nil, err) if the file exists with billing enabled but is misconfigured.
|
|
func LoadBillingConfig(configPath string) (*BillingConfig, error) {
|
|
if configPath == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to read config: %w", err)
|
|
}
|
|
|
|
return parseBillingConfig(data)
|
|
}
|
|
|
|
// parseBillingConfig extracts billing fields from hold config YAML bytes.
|
|
// Returns (nil, nil) if billing is not enabled.
|
|
// Returns (nil, err) if billing is enabled but misconfigured.
|
|
func parseBillingConfig(data []byte) (*BillingConfig, error) {
|
|
var raw billingYAML
|
|
if err := yaml.Unmarshal(data, &raw); err != nil {
|
|
return nil, fmt.Errorf("failed to parse config: %w", err)
|
|
}
|
|
|
|
if !raw.Billing.Enabled {
|
|
return nil, nil
|
|
}
|
|
|
|
cfg := &BillingConfig{
|
|
Enabled: true,
|
|
Currency: raw.Billing.Currency,
|
|
SuccessURL: raw.Billing.SuccessURL,
|
|
CancelURL: raw.Billing.CancelURL,
|
|
PlankOwnerCrewTier: raw.Billing.PlankOwnerCrewTier,
|
|
Tiers: raw.Billing.Tiers,
|
|
}
|
|
|
|
if cfg.Tiers == nil {
|
|
cfg.Tiers = make(map[string]BillingTierConfig)
|
|
}
|
|
|
|
// Validate: billing enabled but no tiers have any Stripe prices configured
|
|
hasAnyPrice := false
|
|
for _, tier := range cfg.Tiers {
|
|
if tier.StripePriceMonthly != "" || tier.StripePriceYearly != "" {
|
|
hasAnyPrice = true
|
|
break
|
|
}
|
|
}
|
|
if !hasAnyPrice {
|
|
return nil, fmt.Errorf("billing is enabled but no tiers have Stripe prices configured")
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
// GetTierPricing returns billing info for a tier, or nil if not found.
|
|
func (c *BillingConfig) GetTierPricing(tierKey string) *BillingTierConfig {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
t, ok := c.Tiers[tierKey]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return &t
|
|
}
|
|
|
|
// GetTierByPriceID finds the tier key that contains the given Stripe price ID.
|
|
// Returns empty string if no match.
|
|
func (c *BillingConfig) GetTierByPriceID(priceID string) string {
|
|
if c == nil || priceID == "" {
|
|
return ""
|
|
}
|
|
for key, tier := range c.Tiers {
|
|
if tier.StripePriceMonthly == priceID || tier.StripePriceYearly == priceID {
|
|
return key
|
|
}
|
|
}
|
|
return ""
|
|
}
|