package eshop import ( "bytes" "encoding/xml" "fmt" "io" "net/http" "time" "fotbal-club/internal/config" "fotbal-club/internal/models" ) type PacketaService struct { Config *config.Config } func NewPacketaService(cfg *config.Config) *PacketaService { return &PacketaService{Config: cfg} } const packetaAPIURL = "https://www.zasilkovna.cz/api/rest" // CreatePacket creates a new packet in Packeta system func (s *PacketaService) CreatePacket(order *models.EshopOrder) (string, error) { type PacketAttributes struct { Number string `xml:"number"` Name string `xml:"name"` Surname string `xml:"surname"` Email string `xml:"email"` Phone string `xml:"phone"` AddressID string `xml:"addressId"` Value string `xml:"value"` Currency string `xml:"currency"` Weight string `xml:"weight"` Eshop string `xml:"eshop"` } type CreatePacketRequest struct { XMLName xml.Name `xml:"createPacket"` ApiPassword string `xml:"apiPassword"` PacketAttributes PacketAttributes `xml:"packetAttributes"` } // Extract shipping data // order.ShippingDataJSON should contain the selected point ID (addressId) // For now assuming we parse it or it is passed differently. // In a real scenario, we would parse ShippingDataJSON to get the point ID. addressID := "1483" // Fallback/Test ID if parsing fails. TODO: Implement proper parsing. reqBody := CreatePacketRequest{ ApiPassword: s.Config.PacketaAPIPassword, PacketAttributes: PacketAttributes{ Number: order.OrderNumber, Name: order.FirstName, Surname: order.LastName, Email: order.Email, Phone: "", // Phone is optional in our model but required for some carriers. TODO: Add phone to checkout. AddressID: addressID, Value: fmt.Sprintf("%.2f", float64(order.TotalAmountCents)/100.0), Currency: order.Currency, Weight: "1.0", // Default weight 1kg. TODO: Calculate from items. Eshop: s.Config.PacketaEshopName, }, } respBytes, err := s.sendRequest(reqBody) if err != nil { return "", err } type CreatePacketResponse struct { PacketId string `xml:"packetId"` Status string `xml:"status"` Fault string `xml:"fault"` } var resp CreatePacketResponse if err := xml.Unmarshal(respBytes, &resp); err != nil { return "", fmt.Errorf("failed to parse response: %w", err) } if resp.Status != "ok" && resp.PacketId == "" { return "", fmt.Errorf("packeta error: %s", resp.Fault) } return resp.PacketId, nil } // GetPacketLabel downloads the label PDF func (s *PacketaService) GetPacketLabel(packetID string) ([]byte, error) { type PacketLabelPdfRequest struct { XMLName xml.Name `xml:"packetLabelPdf"` ApiPassword string `xml:"apiPassword"` PacketId string `xml:"packetId"` Format string `xml:"format"` Offset int `xml:"offset"` } reqBody := PacketLabelPdfRequest{ ApiPassword: s.Config.PacketaAPIPassword, PacketId: packetID, Format: "A6 on A4", Offset: 0, } return s.sendRequest(reqBody) } // GetPacketStatus checks the status of a packet func (s *PacketaService) GetPacketStatus(packetID string) (string, error) { type PacketStatusRequest struct { XMLName xml.Name `xml:"packetStatus"` ApiPassword string `xml:"apiPassword"` PacketId string `xml:"packetId"` } reqBody := PacketStatusRequest{ ApiPassword: s.Config.PacketaAPIPassword, PacketId: packetID, } respBytes, err := s.sendRequest(reqBody) if err != nil { return "", err } // Simplistic parsing for status text/code // XML response structure varies slightly but usually contains ... // We might need a more robust struct. return string(respBytes), nil } func (s *PacketaService) sendRequest(payload interface{}) ([]byte, error) { xmlBytes, err := xml.Marshal(payload) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Post(packetaAPIURL, "text/xml", bytes.NewReader(xmlBytes)) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body)) } return body, nil }