more cart

This commit is contained in:
2026-07-01 10:40:28 +02:00
parent 75db64ce75
commit b1e99891e9
30 changed files with 1058 additions and 93 deletions
+59 -20
View File
@@ -170,14 +170,29 @@ func main() {
}
}
}
freg := buildFlowRegistry(applier, provider, inventoryReservations, emitPub, emailSender, emailTemplates, logger)
// Email preference checker: when a profile service URL is configured, look up
// customer email preferences before sending transactional emails.
var prefChecker order.EmailPreferenceChecker
if profileURL := os.Getenv("PROFILE_URL"); profileURL != "" {
prefChecker = order.NewPreferencesChecker(profileURL + "/ucp/v1/customers")
logger.Info("email preference checking enabled", "profile_url", profileURL)
}
freg := buildFlowRegistry(applier, provider, inventoryReservations, emitPub, emailSender, emailTemplates, prefChecker, logger)
engine := flow.NewEngine(freg, logger)
def, err := order.EmbeddedFlow("place-and-pay")
if err != nil {
log.Fatalf("load flow: %v", err)
}
flows, err := newFlowStore(flowsDir, engine, map[string]*flow.Definition{def.Name: def})
fulfillDef, err := order.EmbeddedFlow("fulfill-order")
if err != nil {
log.Fatalf("load fulfill flow: %v", err)
}
flows, err := newFlowStore(flowsDir, engine, map[string]*flow.Definition{
def.Name: def,
fulfillDef.Name: fulfillDef,
})
if err != nil {
log.Fatalf("load flows: %v", err)
}
@@ -245,6 +260,14 @@ func main() {
mux.HandleFunc("GET /sagas/flows/{name}", s.handleGetFlow)
mux.HandleFunc("PUT /sagas/flows/{name}", s.handleSaveFlow)
// Email template admin API (used by the backoffice Email Template Manager).
eth := newEmailTemplateHandler(config.EnvString("ORDER_EMAIL_TEMPLATES", "data/order-email-templates"))
mux.HandleFunc("GET /api/order-email-templates", eth.list)
mux.HandleFunc("GET /api/order-email-templates/{name}", eth.get)
mux.HandleFunc("PUT /api/order-email-templates/{name}", eth.save)
mux.HandleFunc("DELETE /api/order-email-templates/{name}", eth.delete)
mux.HandleFunc("POST /api/order-email-templates/{name}/preview", eth.preview)
// Ingest checkout orders from order-queue (Klarna/Adyen fallback).
if amqpURL != "" {
startOrderIngest(context.Background(), amqpURL, s)
@@ -543,9 +566,9 @@ func selectProvider(logger *slog.Logger) order.PaymentProvider {
return order.NewMockProvider()
}
// selectEmailSender picks the flow-email transport. SMTP is the only sender
// implementation for now; when no SMTP host is configured the hook still exists
// in capabilities but errors at run time if a flow tries to use it.
// 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") == "" {
@@ -554,9 +577,6 @@ func selectEmailSender(logger *slog.Logger) (order.EmailSender, error) {
if kind == "" {
kind = "smtp"
}
if kind != "smtp" {
return nil, fmt.Errorf("unsupported ORDER_EMAIL_SENDER %q", kind)
}
fromAddr := strings.TrimSpace(os.Getenv("ORDER_EMAIL_FROM_ADDRESS"))
if fromAddr == "" {
@@ -566,19 +586,38 @@ func selectEmailSender(logger *slog.Logger) (order.EmailSender, error) {
Name: strings.TrimSpace(os.Getenv("ORDER_EMAIL_FROM_NAME")),
Address: fromAddr,
}
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
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)
}
logger.Info("email sender enabled", "kind", kind, "host", config.EnvString("ORDER_EMAIL_SMTP_HOST", ""), "from", from.Address)
return sender, nil
}
func selectInventoryReservationService() (order.InventoryReservationService, error) {