aboutsummaryrefslogtreecommitdiffstats
path: root/cost.go
blob: 202fcde615329ef211dc060658e1028831423c30 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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
}