package main import ( "fmt" "log" "log/slog" "net/mail" "os" "strings" "time" "git.k6n.net/mats/go-cart-actor/internal/ucp" "git.k6n.net/mats/go-cart-actor/pkg/order" "git.k6n.net/mats/platform/config" "git.k6n.net/mats/platform/tax" ) // selectTaxProvider picks the tax provider from the environment. Default is // NordicTaxProvider with SE as the default country (matching the current // Nordic-only footprint). Set TAX_PROVIDER=static for dev and tests // (no country awareness). func selectTaxProvider() tax.Provider { provider := os.Getenv("TAX_PROVIDER") switch provider { case "static": return tax.NewStatic() case "nordic": return tax.NewNordic(config.EnvString("TAX_DEFAULT_COUNTRY", "SE")) default: return tax.NewNordic(config.EnvString("TAX_DEFAULT_COUNTRY", "SE")) } } // selectProvider picks the payment provider from the environment. Default is // the dependency-free mock; set PAYMENT_PROVIDER=stripe + STRIPE_SECRET_KEY for // real Stripe (STRIPE_API_BASE / STRIPE_PAYMENT_METHOD optionally override). func selectProvider(logger *slog.Logger) order.PaymentProvider { if os.Getenv("PAYMENT_PROVIDER") == "stripe" { key := os.Getenv("STRIPE_SECRET_KEY") if key == "" { logger.Warn("PAYMENT_PROVIDER=stripe but STRIPE_SECRET_KEY is empty; falling back to mock") return order.NewMockProvider() } logger.Info("using stripe payment provider") return order.NewStripeProvider(key, os.Getenv("STRIPE_API_BASE"), os.Getenv("STRIPE_PAYMENT_METHOD")) } return order.NewMockProvider() } // selectEmailSender picks the flow-email transport. Supports SMTP and // MailerSend. When no sender is configured the hook still exists in // capabilities but errors at run time if a flow tries to use it. func selectEmailSender(logger *slog.Logger) (order.EmailSender, error) { kind := config.EnvString("ORDER_EMAIL_SENDER", "") if kind == "" && os.Getenv("ORDER_EMAIL_SMTP_HOST") == "" { return nil, nil } if kind == "" { kind = "smtp" } fromAddr := strings.TrimSpace(os.Getenv("ORDER_EMAIL_FROM_ADDRESS")) if fromAddr == "" { return nil, fmt.Errorf("ORDER_EMAIL_FROM_ADDRESS is required when email sender is configured") } from := mail.Address{ Name: strings.TrimSpace(os.Getenv("ORDER_EMAIL_FROM_NAME")), Address: fromAddr, } switch kind { case "smtp": sender, err := order.NewSMTPEmailSender(order.SMTPEmailSenderConfig{ Host: config.EnvString("ORDER_EMAIL_SMTP_HOST", ""), Port: config.EnvInt("ORDER_EMAIL_SMTP_PORT", 587, func(n int) bool { return n > 0 }), Username: os.Getenv("ORDER_EMAIL_SMTP_USERNAME"), Password: os.Getenv("ORDER_EMAIL_SMTP_PASSWORD"), DefaultFrom: from, Timeout: config.EnvDuration("ORDER_EMAIL_SMTP_TIMEOUT", 10*time.Second), }) if err != nil { return nil, err } logger.Info("email sender enabled", "kind", "smtp", "host", config.EnvString("ORDER_EMAIL_SMTP_HOST", ""), "from", from.Address) return sender, nil case "mailersend": apiKey := os.Getenv("MAILERSEND_API_KEY") if apiKey == "" { return nil, fmt.Errorf("MAILERSEND_API_KEY is required for mailersend sender") } sender, err := order.NewMailerSendEmailSender(apiKey, from) if err != nil { return nil, err } logger.Info("email sender enabled", "kind", "mailersend", "from", from.Address) return sender, nil default: return nil, fmt.Errorf("unsupported ORDER_EMAIL_SENDER %q (supported: smtp, mailersend)", kind) } } func selectInventoryReservationService() (order.InventoryReservationService, error) { return order.NewHTTPInventoryReservationService(config.EnvString("INVENTORY_URL", "http://localhost:8080"), nil) } // loadUCPOrderSigner loads the UCP ECDSA signing key from the path specified in // the UCP_SIGNING_KEY_PATH environment variable. Returns nil if unset or unreadable. func loadUCPOrderSigner() *ucp.SigningConfig { path := os.Getenv("UCP_SIGNING_KEY_PATH") if path == "" { return nil } pemData, err := os.ReadFile(path) if err != nil { log.Printf("ucp signing: cannot read key %s: %v", path, err) return nil } cfg, err := ucp.NewSigningConfigFromPEM(pemData, "k6n-ecdsa-2026") if err != nil { log.Printf("ucp signing: invalid key at %s: %v", path, err) return nil } return cfg }