package mailer import ( "bytes" "context" "encoding/json" "fmt" htmltemplate "html/template" "io" "net/http" "net/url" "strings" texttemplate "text/template" "time" "royal-pop-backend/internal/config" "royal-pop-backend/internal/orders" ) const resendEndpoint = "https://api.resend.com/emails" type ResendMailer struct { apiKey string from string replyTo string storefrontURL string httpClient *http.Client } type resendEmailRequest struct { From string `json:"from"` To []string `json:"to"` Subject string `json:"subject"` HTML string `json:"html,omitempty"` Text string `json:"text,omitempty"` ReplyTo string `json:"reply_to,omitempty"` } type shippedEmailTemplateData struct { FirstName string OrderID string ShippedDate string ShippingCarrier string TrackingNumber string TrackingURL string OrderImageURL string OrderImageAlt string Items []string StorefrontURL string } type cancelledEmailTemplateData struct { FirstName string OrderID string CancelledDate string RefundAmount string OrderImageURL string OrderImageAlt string Items []string StorefrontURL string } type paidEmailTemplateData struct { FirstName string OrderID string PaidDate string PaidAmount string OrderImageURL string OrderImageAlt string Items []string StorefrontURL string } type createdEmailTemplateData struct { OrderID string CreatedDate string OrderAmount string CustomerName string CustomerEmail string OrderImageURL string OrderImageAlt string Items []string StorefrontURL string ClientDashboardURL string } type deliveredEmailTemplateData struct { FirstName string OrderID string DeliveredDate string OrderImageURL string OrderImageAlt string Items []string StorefrontURL string } var shippedEmailHTMLTemplate = htmltemplate.Must(htmltemplate.New("shipped-email-html").Parse(`

Hi {{.FirstName}},

Your Royal Pop order is now on the way.

Order reference: {{.OrderID}}
Shipped: {{.ShippedDate}}
Carrier: {{.ShippingCarrier}}
Tracking number: {{.TrackingNumber}}

{{if .OrderImageURL}}
{{.OrderImageAlt}}
{{end}} {{if .TrackingURL}}

Track your package

{{end}}

In this shipment

If you need anything, just reply to this email and we’ll help.

Royal Pop

`)) var shippedEmailTextTemplate = texttemplate.Must(texttemplate.New("shipped-email-text").Parse(`Hi {{.FirstName}}, Your Royal Pop order is now on the way. Order reference: {{.OrderID}} Shipped: {{.ShippedDate}} Carrier: {{.ShippingCarrier}} Tracking number: {{.TrackingNumber}} {{if .TrackingURL}}Track your package: {{.TrackingURL}} {{end}} In this shipment: {{range .Items}}- {{.}} {{end}} If you need anything, just reply to this email and we’ll help. Royal Pop `)) var cancelledEmailHTMLTemplate = htmltemplate.Must(htmltemplate.New("cancelled-email-html").Parse(`

Hi {{.FirstName}},

Your Royal Pop order has now been cancelled.

Order reference: {{.OrderID}}
Cancelled: {{.CancelledDate}}

{{if .OrderImageURL}}
{{.OrderImageAlt}}
{{end}}

We were not able to complete this order because some of the information provided was missing or invalid.

A refund of {{.RefundAmount}} has now been issued to the original payment method. Depending on your bank or card provider, the refund may take a few business days to appear.

This cancelled order included

If you would still like the order, just reply to this email and we’ll help you sort it out.

Royal Pop

`)) var cancelledEmailTextTemplate = texttemplate.Must(texttemplate.New("cancelled-email-text").Parse(`Hi {{.FirstName}}, Your Royal Pop order has now been cancelled. Order reference: {{.OrderID}} Cancelled: {{.CancelledDate}} We were not able to complete this order because some of the information provided was missing or invalid. A refund of {{.RefundAmount}} has now been issued to the original payment method. Depending on your bank or card provider, the refund may take a few business days to appear. This cancelled order included: {{range .Items}}- {{.}} {{end}} If you would still like the order, just reply to this email and we’ll help you sort it out. Royal Pop `)) var paidEmailHTMLTemplate = htmltemplate.Must(htmltemplate.New("paid-email-html").Parse(`

Hi {{.FirstName}},

We’ve received your Royal Pop order and your payment has gone through successfully.

Order reference: {{.OrderID}}
Paid: {{.PaidDate}}
Amount received: {{.PaidAmount}}

{{if .OrderImageURL}}
{{.OrderImageAlt}}
{{end}}

Your order details

We’ll email you again as soon as your order has been packed and shipped.

If you need anything in the meantime, just reply to this email and we’ll help.

Royal Pop

`)) var paidEmailTextTemplate = texttemplate.Must(texttemplate.New("paid-email-text").Parse(`Hi {{.FirstName}}, We’ve received your Royal Pop order and your payment has gone through successfully. Order reference: {{.OrderID}} Paid: {{.PaidDate}} Amount received: {{.PaidAmount}} Your order details: {{range .Items}}- {{.}} {{end}} We’ll email you again as soon as your order has been packed and shipped. If you need anything in the meantime, just reply to this email and we’ll help. Royal Pop `)) var createdEmailHTMLTemplate = htmltemplate.Must(htmltemplate.New("created-email-html").Parse(`

Hi,

A new paid Royal Pop order has been created and is ready for review in the client dashboard.

Order reference: {{.OrderID}}
Created: {{.CreatedDate}}
Order total: {{.OrderAmount}}
Customer: {{.CustomerName}}
Customer email: {{.CustomerEmail}}

{{if .OrderImageURL}}
{{.OrderImageAlt}}
{{end}} {{if .ClientDashboardURL}}

Open client dashboard

{{end}}

Your order details

Use the dashboard to review the order, check stock, and continue fulfilment.

If you need anything, just reply to this email and we’ll help.

Royal Pop

`)) var createdEmailTextTemplate = texttemplate.Must(texttemplate.New("created-email-text").Parse(`Hi, A new paid Royal Pop order has been created and is ready for review in the client dashboard. Order reference: {{.OrderID}} Created: {{.CreatedDate}} Order total: {{.OrderAmount}} Customer: {{.CustomerName}} Customer email: {{.CustomerEmail}} {{if .ClientDashboardURL}}Open client dashboard: {{.ClientDashboardURL}} {{end}} Your order details: {{range .Items}}- {{.}} {{end}} Use the dashboard to review the order, check stock, and continue fulfilment. If you need anything, just reply to this email and we’ll help. Royal Pop `)) var deliveredEmailHTMLTemplate = htmltemplate.Must(htmltemplate.New("delivered-email-html").Parse(`

Hi {{.FirstName}},

Your Royal Pop order has been delivered.

Order reference: {{.OrderID}}
Delivered: {{.DeliveredDate}}

{{if .OrderImageURL}}
{{.OrderImageAlt}}
{{end}}

Your delivered order

Thank you for your order — we hope you love it.

If you need anything at all, just reply to this email and we’ll help.

Royal Pop

`)) var deliveredEmailTextTemplate = texttemplate.Must(texttemplate.New("delivered-email-text").Parse(`Hi {{.FirstName}}, Your Royal Pop order has been delivered. Order reference: {{.OrderID}} Delivered: {{.DeliveredDate}} Your delivered order: {{range .Items}}- {{.}} {{end}} Thank you for your order — we hope you love it. If you need anything at all, just reply to this email and we’ll help. Royal Pop `)) var colorwayNames = map[string]string{ "ocho-negro": "ONYX", "pure-white": "BLANC", "pop-pink": "SAKURA", "racer-green": "FOREST", "lime-blue": "SAGE", "deep-blue-orange": "MIDNIGHT", "light-blue-sprint": "GLACIER", "sorbet-pop-multi-color": "SORBET", } var finishNames = map[string]string{ "silver": "Silver", "black-pvd": "Black PVD", "rose-gold": "Rose Gold", } func NewResendMailer(cfg *config.Config) *ResendMailer { if cfg == nil { return &ResendMailer{} } return &ResendMailer{ apiKey: strings.TrimSpace(cfg.ResendAPIKey), from: strings.TrimSpace(cfg.ResendOrderFrom), replyTo: strings.TrimSpace(cfg.ResendForward), storefrontURL: normalizeStorefrontURL(cfg.StorefrontURL), httpClient: &http.Client{ Timeout: 15 * time.Second, }, } } func (m *ResendMailer) Enabled() bool { if m == nil { return false } return strings.TrimSpace(m.apiKey) != "" && strings.TrimSpace(m.from) != "" } func (m *ResendMailer) SendOrderShipped(ctx context.Context, order *orders.Order) error { if order == nil { return fmt.Errorf("order is required") } if !m.Enabled() { return fmt.Errorf("resend mailer is not configured") } payload, err := m.buildShippedEmailRequest(order) if err != nil { return err } body, err := json.Marshal(payload) if err != nil { return fmt.Errorf("marshal resend payload: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, resendEndpoint, bytes.NewReader(body)) if err != nil { return fmt.Errorf("create resend request: %w", err) } req.Header.Set("Authorization", "Bearer "+m.apiKey) req.Header.Set("Content-Type", "application/json") resp, err := m.httpClient.Do(req) if err != nil { return fmt.Errorf("send resend request: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil } responseBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) message := strings.TrimSpace(string(responseBody)) if message == "" { message = resp.Status } return fmt.Errorf("resend email failed: %s", message) } func (m *ResendMailer) SendOrderCancelled(ctx context.Context, order *orders.Order) error { if order == nil { return fmt.Errorf("order is required") } if !m.Enabled() { return fmt.Errorf("resend mailer is not configured") } payload, err := m.buildCancelledEmailRequest(order) if err != nil { return err } body, err := json.Marshal(payload) if err != nil { return fmt.Errorf("marshal resend payload: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, resendEndpoint, bytes.NewReader(body)) if err != nil { return fmt.Errorf("create resend request: %w", err) } req.Header.Set("Authorization", "Bearer "+m.apiKey) req.Header.Set("Content-Type", "application/json") resp, err := m.httpClient.Do(req) if err != nil { return fmt.Errorf("send resend request: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil } responseBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) message := strings.TrimSpace(string(responseBody)) if message == "" { message = resp.Status } return fmt.Errorf("resend email failed: %s", message) } func (m *ResendMailer) SendOrderPaid(ctx context.Context, order *orders.Order) error { if order == nil { return fmt.Errorf("order is required") } if !m.Enabled() { return fmt.Errorf("resend mailer is not configured") } payload, err := m.buildPaidEmailRequest(order) if err != nil { return err } body, err := json.Marshal(payload) if err != nil { return fmt.Errorf("marshal resend payload: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, resendEndpoint, bytes.NewReader(body)) if err != nil { return fmt.Errorf("create resend request: %w", err) } req.Header.Set("Authorization", "Bearer "+m.apiKey) req.Header.Set("Content-Type", "application/json") resp, err := m.httpClient.Do(req) if err != nil { return fmt.Errorf("send resend request: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil } responseBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) message := strings.TrimSpace(string(responseBody)) if message == "" { message = resp.Status } return fmt.Errorf("resend email failed: %s", message) } func (m *ResendMailer) SendOrderCreated(ctx context.Context, order *orders.Order) error { if order == nil { return fmt.Errorf("order is required") } if !m.Enabled() { return fmt.Errorf("resend mailer is not configured") } payload, err := m.buildCreatedEmailRequest(order) if err != nil { return err } body, err := json.Marshal(payload) if err != nil { return fmt.Errorf("marshal resend payload: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, resendEndpoint, bytes.NewReader(body)) if err != nil { return fmt.Errorf("create resend request: %w", err) } req.Header.Set("Authorization", "Bearer "+m.apiKey) req.Header.Set("Content-Type", "application/json") resp, err := m.httpClient.Do(req) if err != nil { return fmt.Errorf("send resend request: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil } responseBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) message := strings.TrimSpace(string(responseBody)) if message == "" { message = resp.Status } return fmt.Errorf("resend email failed: %s", message) } func (m *ResendMailer) SendOrderDelivered(ctx context.Context, order *orders.Order) error { if order == nil { return fmt.Errorf("order is required") } if !m.Enabled() { return fmt.Errorf("resend mailer is not configured") } payload, err := m.buildDeliveredEmailRequest(order) if err != nil { return err } body, err := json.Marshal(payload) if err != nil { return fmt.Errorf("marshal resend payload: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, resendEndpoint, bytes.NewReader(body)) if err != nil { return fmt.Errorf("create resend request: %w", err) } req.Header.Set("Authorization", "Bearer "+m.apiKey) req.Header.Set("Content-Type", "application/json") resp, err := m.httpClient.Do(req) if err != nil { return fmt.Errorf("send resend request: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil } responseBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) message := strings.TrimSpace(string(responseBody)) if message == "" { message = resp.Status } return fmt.Errorf("resend email failed: %s", message) } func (m *ResendMailer) buildShippedEmailRequest(order *orders.Order) (*resendEmailRequest, error) { data := shippedEmailTemplateData{ FirstName: firstNonEmpty(strings.TrimSpace(order.FirstName), "there"), OrderID: strings.TrimSpace(order.ID), ShippedDate: shippedDateLabel(order), ShippingCarrier: strings.TrimSpace(order.ShippingCarrier), TrackingNumber: strings.TrimSpace(order.TrackingNumber), TrackingURL: buildTrackingURL(order.ShippingCarrier, order.TrackingNumber), OrderImageURL: buildOrderImageURL(m.storefrontURL, order.Items), OrderImageAlt: buildOrderImageAlt(order.Items), Items: buildShipmentLineItems(order.Items), StorefrontURL: m.storefrontURL, } var htmlBody bytes.Buffer if err := shippedEmailHTMLTemplate.Execute(&htmlBody, data); err != nil { return nil, fmt.Errorf("render shipment email html: %w", err) } var textBody bytes.Buffer if err := shippedEmailTextTemplate.Execute(&textBody, data); err != nil { return nil, fmt.Errorf("render shipment email text: %w", err) } return &resendEmailRequest{ From: m.from, To: []string{strings.TrimSpace(order.Email)}, Subject: fmt.Sprintf("Your Royal Pop order is on the way — %s", data.OrderID), HTML: htmlBody.String(), Text: textBody.String(), ReplyTo: m.replyTo, }, nil } func (m *ResendMailer) buildCancelledEmailRequest(order *orders.Order) (*resendEmailRequest, error) { data := cancelledEmailTemplateData{ FirstName: firstNonEmpty(strings.TrimSpace(order.FirstName), "there"), OrderID: strings.TrimSpace(order.ID), CancelledDate: cancelledDateLabel(order), RefundAmount: formatMoney(order.Currency, order.Amount), OrderImageURL: buildOrderImageURL(m.storefrontURL, order.Items), OrderImageAlt: buildOrderImageAlt(order.Items), Items: buildShipmentLineItems(order.Items), StorefrontURL: m.storefrontURL, } var htmlBody bytes.Buffer if err := cancelledEmailHTMLTemplate.Execute(&htmlBody, data); err != nil { return nil, fmt.Errorf("render cancelled email html: %w", err) } var textBody bytes.Buffer if err := cancelledEmailTextTemplate.Execute(&textBody, data); err != nil { return nil, fmt.Errorf("render cancelled email text: %w", err) } return &resendEmailRequest{ From: m.from, To: []string{strings.TrimSpace(order.Email)}, Subject: fmt.Sprintf("Your Royal Pop order has been cancelled — %s", data.OrderID), HTML: htmlBody.String(), Text: textBody.String(), ReplyTo: m.replyTo, }, nil } func (m *ResendMailer) buildPaidEmailRequest(order *orders.Order) (*resendEmailRequest, error) { data := paidEmailTemplateData{ FirstName: firstNonEmpty(strings.TrimSpace(order.FirstName), "there"), OrderID: strings.TrimSpace(order.ID), PaidDate: paidDateLabel(order), PaidAmount: formatMoney(order.Currency, order.Amount), OrderImageURL: buildOrderImageURL(m.storefrontURL, order.Items), OrderImageAlt: buildOrderImageAlt(order.Items), Items: buildShipmentLineItems(order.Items), StorefrontURL: m.storefrontURL, } var htmlBody bytes.Buffer if err := paidEmailHTMLTemplate.Execute(&htmlBody, data); err != nil { return nil, fmt.Errorf("render paid email html: %w", err) } var textBody bytes.Buffer if err := paidEmailTextTemplate.Execute(&textBody, data); err != nil { return nil, fmt.Errorf("render paid email text: %w", err) } return &resendEmailRequest{ From: m.from, To: []string{strings.TrimSpace(order.Email)}, Subject: fmt.Sprintf("We’ve received your Royal Pop order — %s", data.OrderID), HTML: htmlBody.String(), Text: textBody.String(), ReplyTo: m.replyTo, }, nil } func (m *ResendMailer) buildCreatedEmailRequest(order *orders.Order) (*resendEmailRequest, error) { data := createdEmailTemplateData{ OrderID: strings.TrimSpace(order.ID), CreatedDate: createdDateLabel(order), OrderAmount: formatMoney(order.Currency, order.Amount), CustomerName: strings.TrimSpace(strings.Join([]string{strings.TrimSpace(order.FirstName), strings.TrimSpace(order.LastName)}, " ")), CustomerEmail: strings.TrimSpace(order.Email), OrderImageURL: buildOrderImageURL(m.storefrontURL, order.Items), OrderImageAlt: buildOrderImageAlt(order.Items), Items: buildShipmentLineItems(order.Items), StorefrontURL: m.storefrontURL, ClientDashboardURL: buildClientDashboardURL(m.storefrontURL), } if data.CustomerName == "" { data.CustomerName = "Customer" } if data.CustomerEmail == "" { data.CustomerEmail = "Not provided" } var htmlBody bytes.Buffer if err := createdEmailHTMLTemplate.Execute(&htmlBody, data); err != nil { return nil, fmt.Errorf("render created email html: %w", err) } var textBody bytes.Buffer if err := createdEmailTextTemplate.Execute(&textBody, data); err != nil { return nil, fmt.Errorf("render created email text: %w", err) } return &resendEmailRequest{ From: m.from, To: []string{firstNonEmpty(strings.TrimSpace(m.replyTo), strings.TrimSpace(m.from))}, Subject: fmt.Sprintf("Client reminder: paid order created — %s", data.OrderID), HTML: htmlBody.String(), Text: textBody.String(), ReplyTo: m.replyTo, }, nil } func (m *ResendMailer) buildDeliveredEmailRequest(order *orders.Order) (*resendEmailRequest, error) { data := deliveredEmailTemplateData{ FirstName: firstNonEmpty(strings.TrimSpace(order.FirstName), "there"), OrderID: strings.TrimSpace(order.ID), DeliveredDate: deliveredDateLabel(order), OrderImageURL: buildOrderImageURL(m.storefrontURL, order.Items), OrderImageAlt: buildOrderImageAlt(order.Items), Items: buildShipmentLineItems(order.Items), StorefrontURL: m.storefrontURL, } var htmlBody bytes.Buffer if err := deliveredEmailHTMLTemplate.Execute(&htmlBody, data); err != nil { return nil, fmt.Errorf("render delivered email html: %w", err) } var textBody bytes.Buffer if err := deliveredEmailTextTemplate.Execute(&textBody, data); err != nil { return nil, fmt.Errorf("render delivered email text: %w", err) } return &resendEmailRequest{ From: m.from, To: []string{strings.TrimSpace(order.Email)}, Subject: fmt.Sprintf("Your Royal Pop order has been delivered — %s", data.OrderID), HTML: htmlBody.String(), Text: textBody.String(), ReplyTo: m.replyTo, }, nil } func buildClientDashboardURL(storefrontURL string) string { base := strings.TrimRight(strings.TrimSpace(storefrontURL), "/") if base == "" { base = "https://royal-pop-accessory.com" } return base + "/client" } func buildShipmentLineItems(items []orders.LineItem) []string { if len(items) == 0 { return []string{"Royal Pop order items"} } lines := make([]string, 0, len(items)) for _, item := range items { quantity := item.Quantity if quantity <= 0 { quantity = 1 } styleLabel := "Style " + strings.ToUpper(strings.TrimSpace(item.Style)) if strings.TrimSpace(item.Style) == "" { styleLabel = "Style" } lines = append(lines, fmt.Sprintf( "%s · %s · %s · %s", firstNonEmpty(colorwayDisplayName(item.ColorwayID), "Royal Pop"), styleLabel, firstNonEmpty(finishDisplayName(item.FinishID), "Finish"), quantityLabel(quantity), )) } return lines } func shippedDateLabel(order *orders.Order) string { if order == nil { return time.Now().UTC().Format("2 January 2006") } if order.ShippedAt != nil && !order.ShippedAt.IsZero() { return order.ShippedAt.UTC().Format("2 January 2006") } if !order.UpdatedAt.IsZero() { return order.UpdatedAt.UTC().Format("2 January 2006") } return time.Now().UTC().Format("2 January 2006") } func cancelledDateLabel(order *orders.Order) string { if order == nil { return time.Now().UTC().Format("2 January 2006") } if !order.UpdatedAt.IsZero() { return order.UpdatedAt.UTC().Format("2 January 2006") } return time.Now().UTC().Format("2 January 2006") } func paidDateLabel(order *orders.Order) string { if order == nil { return time.Now().UTC().Format("2 January 2006") } if !order.UpdatedAt.IsZero() { return order.UpdatedAt.UTC().Format("2 January 2006") } if !order.CreatedAt.IsZero() { return order.CreatedAt.UTC().Format("2 January 2006") } return time.Now().UTC().Format("2 January 2006") } func createdDateLabel(order *orders.Order) string { if order == nil { return time.Now().UTC().Format("2 January 2006") } if !order.CreatedAt.IsZero() { return order.CreatedAt.UTC().Format("2 January 2006") } if !order.UpdatedAt.IsZero() { return order.UpdatedAt.UTC().Format("2 January 2006") } return time.Now().UTC().Format("2 January 2006") } func deliveredDateLabel(order *orders.Order) string { if order == nil { return time.Now().UTC().Format("2 January 2006") } if !order.UpdatedAt.IsZero() { return order.UpdatedAt.UTC().Format("2 January 2006") } if order.ShippedAt != nil && !order.ShippedAt.IsZero() { return order.ShippedAt.UTC().Format("2 January 2006") } return time.Now().UTC().Format("2 January 2006") } func quantityLabel(quantity int64) string { if quantity == 1 { return "1 kit" } return fmt.Sprintf("%d kits", quantity) } func formatMoney(currency string, amount int64) string { code := strings.ToUpper(strings.TrimSpace(currency)) major := float64(amount) / 100 switch code { case "GBP", "": return fmt.Sprintf("£%.2f", major) case "USD": return fmt.Sprintf("$%.2f", major) case "EUR": return fmt.Sprintf("€%.2f", major) default: return fmt.Sprintf("%s %.2f", code, major) } } func colorwayDisplayName(id string) string { trimmed := strings.TrimSpace(id) if trimmed == "" { return "" } if label, ok := colorwayNames[trimmed]; ok { return label } return slugLabel(trimmed) } func finishDisplayName(id string) string { trimmed := strings.TrimSpace(id) if trimmed == "" { return "" } if label, ok := finishNames[trimmed]; ok { return label } return slugLabel(trimmed) } func slugLabel(value string) string { parts := strings.Fields(strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(value), "-", " "), "_", " ")) if len(parts) == 0 { return "" } formatted := make([]string, 0, len(parts)) for _, part := range parts { formatted = append(formatted, strings.ToUpper(part[:1])+strings.ToLower(part[1:])) } return strings.Join(formatted, " ") } func firstNonEmpty(values ...string) string { for _, value := range values { if trimmed := strings.TrimSpace(value); trimmed != "" { return trimmed } } return "" } func normalizeStorefrontURL(raw string) string { trimmed := strings.TrimRight(strings.TrimSpace(raw), "/") if trimmed == "" { return "https://royal-pop-accessory.com" } lower := strings.ToLower(trimmed) if strings.Contains(lower, "localhost") || strings.Contains(lower, "127.0.0.1") || strings.Contains(lower, ".local") { return "https://royal-pop-accessory.com" } return trimmed } func buildTrackingURL(carrier, trackingNumber string) string { tracking := strings.TrimSpace(trackingNumber) if tracking == "" { return "" } return "https://www.17track.net/en/track#nums=" + url.QueryEscape(tracking) } func buildOrderImageURL(storefrontURL string, items []orders.LineItem) string { baseURL := strings.TrimRight(strings.TrimSpace(storefrontURL), "/") if baseURL == "" { return "" } imagePath := "/images/png/ocho-negro.png" if len(items) > 0 { colorwayID := strings.TrimSpace(items[0].ColorwayID) if mappedPath, ok := orderImagePathForColorway(colorwayID); ok { imagePath = mappedPath } } return baseURL + imagePath } func orderImagePathForColorway(colorwayID string) (string, bool) { switch strings.TrimSpace(colorwayID) { case "ocho-negro": return "/images/png/ocho-negro.png", true case "pure-white": return "/images/png/pure-white.png", true case "pop-pink": return "/images/png/pop-pink.png", true case "racer-green": return "/images/png/racer-green.png", true case "lime-blue": return "/images/png/lime-blue.png", true case "deep-blue-orange": return "/images/png/deep-blue.png", true case "light-blue-sprint": return "/images/png/light-blue.png", true case "sorbet-pop-multi-color": return "/images/png/sorbet-multi.png", true default: return "", false } } func buildOrderImageAlt(items []orders.LineItem) string { if len(items) == 0 { return "Royal Pop order preview" } colorway := colorwayDisplayName(items[0].ColorwayID) style := strings.ToUpper(strings.TrimSpace(items[0].Style)) finish := finishDisplayName(items[0].FinishID) parts := make([]string, 0, 3) if colorway != "" { parts = append(parts, colorway) } if style != "" { parts = append(parts, "Style "+style) } if finish != "" { parts = append(parts, finish) } if len(parts) == 0 { return "Royal Pop order preview" } return strings.Join(parts, " · ") + " order preview" }