diff --git a/billing.go b/billing.go new file mode 100644 index 0000000..f9475d7 --- /dev/null +++ b/billing.go @@ -0,0 +1,159 @@ +package govultr + +import ( + "context" + "fmt" + "net/http" + + "github.com/google/go-querystring/query" +) + +// BillingService is the interface to interact with the billing endpoint on the Vultr API +// Link : https://www.vultr.com/api/#tag/billing +type BillingService interface { + ListHistory(ctx context.Context, options *ListOptions) ([]History, *Meta, error) + ListInvoices(ctx context.Context, options *ListOptions) ([]Invoice, *Meta, error) + GetInvoice(ctx context.Context, invoiceID string) (*Invoice, error) + ListInvoiceItems(ctx context.Context, invoiceID int, options *ListOptions) ([]InvoiceItem, *Meta, error) +} + +// BillingServiceHandler handles interaction with the billing methods for the Vultr API +type BillingServiceHandler struct { + client *Client +} + +// History represents a billing history item on an account +type History struct { + ID int `json:"id"` + Date string `json:"date"` + Type string `json:"type"` + Description string `json:"description"` + Amount float32 `json:"amount"` + Balance float32 `json:"balance"` +} + +// Invoice represents an invoice on an account +type Invoice struct { + ID int `json:"id"` + Date string `json:"date"` + Description string `json:"description"` + Amount float32 `json:"amount"` + Balance float32 `json:"balance"` +} + +// InvoiceItem represents an item on an accounts invoice +type InvoiceItem struct { + Description string `json:"description"` + Product string `json:"product"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Units int `json:"units"` + UnitType string `json:"unit_type"` + UnitPrice float32 `json:"unit_price"` + Total float32 `json:"total"` +} + +type billingHistoryBase struct { + History []History `json:"billing_history"` + Meta *Meta `json:"meta"` +} + +type invoicesBase struct { + Invoice []Invoice `json:"billing_invoices"` + Meta *Meta `json:"meta"` +} + +type invoiceBase struct { + Invoice *Invoice `json:"billing_invoice"` +} + +type invoiceItemsBase struct { + InvoiceItems []InvoiceItem `json:"invoice_items"` + Meta *Meta `json:"meta"` +} + +// ListHistory retrieves a list of all billing history on the current account +func (b *BillingServiceHandler) ListHistory(ctx context.Context, options *ListOptions) ([]History, *Meta, error) { + uri := "/v2/billing/history" + req, err := b.client.NewRequest(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, nil, err + } + + newValues, err := query.Values(options) + if err != nil { + return nil, nil, err + } + + req.URL.RawQuery = newValues.Encode() + + invoices := new(billingHistoryBase) + if err = b.client.DoWithContext(ctx, req, invoices); err != nil { + return nil, nil, err + } + + return invoices.History, invoices.Meta, nil +} + +// ListInvoices retrieves a list of all billing invoices on the current account +func (b *BillingServiceHandler) ListInvoices(ctx context.Context, options *ListOptions) ([]Invoice, *Meta, error) { + uri := "/v2/billing/invoices" + req, err := b.client.NewRequest(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, nil, err + } + + newValues, err := query.Values(options) + if err != nil { + return nil, nil, err + } + + req.URL.RawQuery = newValues.Encode() + + invoices := new(invoicesBase) + if err = b.client.DoWithContext(ctx, req, invoices); err != nil { + return nil, nil, err + } + + return invoices.Invoice, invoices.Meta, nil +} + +// GetInvoice retrieves an invoice that matches the given invoiceID +func (b *BillingServiceHandler) GetInvoice(ctx context.Context, invoiceID string) (*Invoice, error) { + uri := fmt.Sprintf("/v2/billing/invoices/%s", invoiceID) + req, err := b.client.NewRequest(ctx, http.MethodGet, uri, nil) + + if err != nil { + return nil, err + } + + invoice := new(invoiceBase) + if err := b.client.DoWithContext(ctx, req, invoice); err != nil { + return nil, err + } + + return invoice.Invoice, nil +} + +// ListInvoiceItems retrieves items in an invoice that matches the given invoiceID +func (b *BillingServiceHandler) ListInvoiceItems(ctx context.Context, invoiceID int, options *ListOptions) ([]InvoiceItem, *Meta, error) { + uri := fmt.Sprintf("/v2/billing/invoices/%d/items", invoiceID) + req, err := b.client.NewRequest(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, nil, err + } + + newValues, err := query.Values(options) + if err != nil { + return nil, nil, err + } + + req.URL.RawQuery = newValues.Encode() + + invoice := new(invoiceItemsBase) + if err := b.client.DoWithContext(ctx, req, invoice); err != nil { + return nil, nil, err + } + + return invoice.InvoiceItems, invoice.Meta, nil +} diff --git a/billing_test.go b/billing_test.go new file mode 100644 index 0000000..44aee00 --- /dev/null +++ b/billing_test.go @@ -0,0 +1,323 @@ +package govultr + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestBillingServiceHandler_ListHistory(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/billing/history", func(w http.ResponseWriter, r *http.Request) { + + response := ` + { + "billing_history": [ + { + "id": 5317720, + "date": "2018-04-01T00:30:05+00:00", + "type": "invoice", + "description": "Invoice #5317720", + "amount": 2.35, + "balance": -497.65 + } + ], + "meta": { + "total":1, + "links": { + "next":"", + "prev":"" + } + } + } + ` + + fmt.Fprint(w, response) + }) + + history, meta, err := client.Billing.ListHistory(ctx, nil) + if err != nil { + t.Errorf("Billing.ListHistory returned error: %v", err) + } + + expected := []History{ + { + ID: 5317720, + Date: "2018-04-01T00:30:05+00:00", + Type: "invoice", + Description: "Invoice #5317720", + Amount: 2.35, + Balance: -497.65, + }, + } + + expectedMeta := &Meta{ + Total: 1, + Links: &Links{}, + } + + if !reflect.DeepEqual(history, expected) { + t.Errorf("Billing.ListHistory returned %+v, expected %+v", history, expected) + } + + if !reflect.DeepEqual(meta, expectedMeta) { + t.Errorf("Billing.ListHistory returned %+v, expected %+v", meta, expectedMeta) + } +} + +func TestBillingServiceHandler_ListInvoices(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/billing/invoices", func(w http.ResponseWriter, r *http.Request) { + + response := ` + { + "billing_invoices": [ + { + "id": 5317720, + "date": "2018-04-01T00:30:05+00:00", + "description": "Invoice #5317720", + "amount": 2.35, + "balance": -497.65 + } + ], + "meta": { + "total":1, + "links": { + "next":"", + "prev":"" + } + } + } + ` + + fmt.Fprint(w, response) + }) + + invoices, meta, err := client.Billing.ListInvoices(ctx, nil) + if err != nil { + t.Errorf("Billing.ListInvoices returned error: %v", err) + } + + expected := []Invoice{ + { + ID: 5317720, + Date: "2018-04-01T00:30:05+00:00", + Description: "Invoice #5317720", + Amount: 2.35, + Balance: -497.65, + }, + } + + expectedMeta := &Meta{ + Total: 1, + Links: &Links{}, + } + + if !reflect.DeepEqual(invoices, expected) { + t.Errorf("Billing.ListInvoices returned %+v, expected %+v", invoices, expected) + } + + if !reflect.DeepEqual(meta, expectedMeta) { + t.Errorf("Billing.ListInvoices returned %+v, expected %+v", meta, expectedMeta) + } +} + +func TestBillingServiceHandler_ListHistoryEmpty(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/billing/history", func(w http.ResponseWriter, r *http.Request) { + + response := ` + { + "billing_history": [], + "meta": { + "total":0, + "links": { + "next":"", + "prev":"" + } + } + } + ` + + fmt.Fprint(w, response) + }) + + history, meta, err := client.Billing.ListHistory(ctx, nil) + if err != nil { + t.Errorf("Billing.ListHistory returned error: %v", err) + } + + expected := []History{} + + if !reflect.DeepEqual(history, expected) { + t.Errorf("Billing.ListHistory returned %+v, expected %+v", history, expected) + } + + expectedMeta := &Meta{ + Total: 0, + Links: &Links{ + Next: "", + Prev: "", + }, + } + + if !reflect.DeepEqual(meta, expectedMeta) { + t.Errorf("Billing.ListHistory meta returned %+v, expected %+v", meta, expectedMeta) + } +} + +func TestBillingServiceHandler_ListInvoicesEmpty(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/billing/invoices", func(w http.ResponseWriter, r *http.Request) { + + response := ` + { + "billing_invoices": [], + "meta": { + "total":0, + "links": { + "next":"", + "prev":"" + } + } + } + ` + + fmt.Fprint(w, response) + }) + + invoices, meta, err := client.Billing.ListInvoices(ctx, nil) + if err != nil { + t.Errorf("Billing.ListInvoices returned error: %v", err) + } + + expected := []Invoice{} + + if !reflect.DeepEqual(invoices, expected) { + t.Errorf("Billing.ListInvoices returned %+v, expected %+v", invoices, expected) + } + + expectedMeta := &Meta{ + Total: 0, + Links: &Links{ + Next: "", + Prev: "", + }, + } + + if !reflect.DeepEqual(meta, expectedMeta) { + t.Errorf("Billing.ListInvoices meta returned %+v, expected %+v", meta, expectedMeta) + } +} + +func TestBillingServiceHandler_GetInvoice(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/billing/invoices/123456", func(w http.ResponseWriter, r *http.Request) { + + response := ` + { + "billing_invoice": { + "id": 123456, + "date": "2018-04-01T00:30:05+00:00", + "description": "Invoice #5317782", + "amount": 2.35, + "balance": -497.65 + } + } + ` + + fmt.Fprint(w, response) + }) + + invoice, err := client.Billing.GetInvoice(ctx, "123456") + if err != nil { + t.Errorf("Billing.GetInvoice returned error: %v", err) + } + + expected := &Invoice{ + ID: 123456, + Date: "2018-04-01T00:30:05+00:00", + Description: "Invoice #5317782", + Amount: 2.35, + Balance: -497.65, + } + + if !reflect.DeepEqual(invoice, expected) { + t.Errorf("Billing.GetInvoice returned %+v, expected %+v", invoice, expected) + } +} + +func TestBillingServiceHandler_ListInvoiceItems(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/billing/invoices/123456/items", func(w http.ResponseWriter, r *http.Request) { + + response := ` + { + "invoice_items": [ + { + "description": "1.1.1.1 (1024 MB)", + "product": "Vultr Cloud Compute", + "start_date": "2018-03-18T21:57:58+00:00", + "end_date": "2018-04-01T00:00:00+00:00", + "units": 315, + "unit_type": "hours", + "unit_price": 0.0074, + "total": 2.35 + } + ], + "meta": { + "total":1, + "links": { + "next":"", + "prev":"" + } + } + } + ` + + fmt.Fprint(w, response) + }) + invoices, meta, err := client.Billing.ListInvoiceItems(ctx, 123456, nil) + if err != nil { + t.Errorf("Billing.ListInvoiceItems returned error: %v", err) + } + + expected := []InvoiceItem{ + { + Description: "1.1.1.1 (1024 MB)", + Product: "Vultr Cloud Compute", + StartDate: "2018-03-18T21:57:58+00:00", + EndDate: "2018-04-01T00:00:00+00:00", + Units: 315, + UnitType: "hours", + UnitPrice: 0.0074, + Total: 2.35, + }, + } + + expectedMeta := &Meta{ + Total: 1, + Links: &Links{}, + } + + if !reflect.DeepEqual(invoices, expected) { + t.Errorf("Billing.ListInvoiceItems returned %+v, expected %+v", invoices, expected) + } + + if !reflect.DeepEqual(meta, expectedMeta) { + t.Errorf("Billing.ListInvoiceItems returned %+v, expected %+v", meta, expectedMeta) + } +} diff --git a/govultr.go b/govultr.go index 759e671..9ede747 100644 --- a/govultr.go +++ b/govultr.go @@ -48,6 +48,7 @@ type Client struct { Application ApplicationService Backup BackupService BareMetalServer BareMetalServerService + Billing BillingService BlockStorage BlockStorageService Domain DomainService DomainRecord DomainRecordService @@ -100,6 +101,7 @@ func NewClient(httpClient *http.Client) *Client { client.Application = &ApplicationServiceHandler{client} client.Backup = &BackupServiceHandler{client} client.BareMetalServer = &BareMetalServerServiceHandler{client} + client.Billing = &BillingServiceHandler{client} client.BlockStorage = &BlockStorageServiceHandler{client} client.Domain = &DomainServiceHandler{client} client.DomainRecord = &DomainRecordsServiceHandler{client}