package lulu // TODO: should we remove the redundant tax/subtotal fields or remain // true to their retarded api? import ( "encoding/json" "fmt" "github.com/shopspring/decimal" ) // printJobCostReq is the json body of a /print-job-cost-calculations/ request. type printJobCostReq struct { LineItems []printJobCostLineItem `json:"line_items"` ShipAddr ShippingAddress `json:"shipping_address"` ShipOpt ShippingLevel `json:"shipping_option"` } type printJobCostLineItem struct { NPages uint `json:"page_count"` Mfg PkgId `json:"pod_package_id"` Quantity uint `json:"quantity"` } // PrintJobCost is the response from /print-job-cost-calculations/. type PrintJobCost struct { Addr, SuggestedAddr ShippingAddress AddrWarning ShippingAddressWarning Currency string Fees []Fee FulfillmentCost FulfillmentCost LineItemCosts []LineItemCost ShipCost FulfillmentCost TotalCostExclTax, TotalCostInclTax, TotalDiscount, TotalTax decimal.Decimal } type ShippingAddressWarning struct { Type string `json:"type"` // eg "validation_warning" Path string `json:"path"` // eg "external" Code string `json:"code"` // eg "REPLACED" Msg string `json:"message"` // eg "street1: Holstenstr. 40 -> Holstenstraße 40" } type Fee struct { Currency string `json:"currency"` Type string `json:"fee_type"` Sku string `json:"sku"` TaxRate decimal.Decimal `json:"tax_rate"` TotalCostExclTax decimal.Decimal `json:"total_cost_excl_tax"` TotalCostInclTax decimal.Decimal `json:"total_cost_incl_tax"` TotalTax decimal.Decimal `json:"total_tax"` } type FulfillmentCost struct { TaxRate decimal.Decimal `json:"tax_rate"` TotalCostExclTax decimal.Decimal `json:"total_cost_excl_tax"` TotalCostInclTax decimal.Decimal `json:"total_cost_incl_tax"` TotalTax decimal.Decimal `json:"total_tax"` } type LineItemCost struct { CostExclDiscounts decimal.Decimal `json:"cost_excl_discounts"` Discounts []Discount `json:"discounts"` Quantity uint `json:"quantity"` TaxRate decimal.Decimal `json:"tax_rate"` TotalCostExclDiscounts decimal.Decimal `json:"total_cost_excl_discounts"` TotalCostExclTax decimal.Decimal `json:"total_cost_excl_tax"` TotalCostInclTax decimal.Decimal `json:"total_cost_incl_tax"` TotalTax decimal.Decimal `json:"total_tax"` UnitTierCost *decimal.Decimal `json:"unit_tier_cost"` } type Discount struct { Amount decimal.Decimal `json:"amount"` Descr string `json:"description"` } // printJobCostResp is the json body of a /print-job-cost-calculations/ response. type printJobCostResp struct { Addr struct { City string `json:"city"` CountryCode string `json:"country_code"` IsBusiness bool `json:"is_business"` Name string `json:"name"` Phone string `json:"phone_number"` PostCode string `json:"postcode"` StateCode string `json:"state_code"` Street1 string `json:"street1"` Street2 string `json:"street2"` Warning ShippingAddressWarning `json:"warnings"` Suggested suggestedShippingAddress `json:"suggested_address"` } `json:"shipping_address"` Currency string `json:"currency"` Fees []Fee `json:"fees"` FulfillmentCost FulfillmentCost `json:"fulfillment_cost"` LineItemCosts []LineItemCost `json:"line_item_costs"` ShipCost FulfillmentCost `json:"shipping_cost"` TotalCostExclTax decimal.Decimal `json:"total_cost_excl_tax"` TotalCostInclTax decimal.Decimal `json:"total_cost_incl_tax"` TotalDiscount decimal.Decimal `json:"total_discount_amount"` TotalTax decimal.Decimal `json:"total_tax"` } // Some blockhead that Lulu hired thought it would be a good idea to use // a string in one place and a number in another for the postal code. // That's why this special struct is needed. type suggestedShippingAddress struct { CountryCode string `json:"country_code"` StateCode string `json:"state_code"` PostCode uint `json:"postcode"` City string `json:"city"` Street1 string `json:"street1"` Street2 string `json:"street2"` } func (c *PrintJobCost) UnmarshalJSON(data []byte) error { // Lulu decided to put warnings and suggested_address INSIDE // shipping_address in the response. So we unmarshal into an // alias struct and then map onto our own structs. The // alternative is to add everything to our ShippingAddress // struct, but that would be confusing to users of this library: // ShippingAddress would have semantics because some fields would // be "optional". var resp printJobCostResp if err := json.Unmarshal(data, &resp); err != nil { return err } addr := resp.Addr c.Addr = ShippingAddress{ City: addr.City, CountryCode: addr.CountryCode, IsBusiness: addr.IsBusiness, Name: addr.Name, Phone: addr.Phone, PostCode: addr.PostCode, StateCode: addr.StateCode, Street1: addr.Street1, Street2: addr.Street2, } sugad := addr.Suggested c.SuggestedAddr = ShippingAddress{ CountryCode: sugad.CountryCode, StateCode: sugad.StateCode, PostCode: fmt.Sprint(sugad.PostCode), // idiot City: sugad.City, Street1: sugad.Street1, Street2: sugad.Street2, } c.AddrWarning = addr.Warning c.Currency = resp.Currency c.Fees = resp.Fees c.FulfillmentCost = resp.FulfillmentCost c.LineItemCosts = resp.LineItemCosts c.ShipCost = resp.ShipCost c.TotalCostExclTax = resp.TotalCostExclTax c.TotalCostInclTax = resp.TotalCostInclTax c.TotalDiscount = resp.TotalDiscount c.TotalTax = resp.TotalTax return nil }