Repository: VictorAvelar/mollie-api-go (go)

Issue:
Implement Invoices API resource
**Description**
Implement the API endpoints related to the invoice resource and provide test coverage, as an example you might take a look at #6 

**Additional context**
The API reference for refunds can be found here: https://docs.mollie.com/reference/v2/invoices-api/get-invoice


Pull request description:
Implement invoices API
<!--- Provide a general summary of your changes in the Title above -->

## Description

Implement invoices API

## Motivation and context

fixes #27 

## How has this been tested?

I added 3 unit test in invoices_test.go: 

## Screenshots (if appropriate)

## Types of changes

What types of changes does your code introduce? Put an `x` in all the boxes that apply:
- [  ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [  ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

Go over all the following points, and put an `x` in all the boxes that apply.

Please, please, please, don't send your pull request until all of the boxes are ticked. Once your pull request is created, it will trigger a build on our continuous integration server to make sure your [tests and code style pass](https://help.github.com/articles/about-required-status-checks/).

- [x] I have read the **[CONTRIBUTING](CONTRIBUTING.md)** document.
- [x] My pull request addresses exactly one patch/feature.
- [x] I have created a branch for this patch/feature.
- [x] Each individual commit in the pull request is meaningful.
- [x] I have added tests to cover my changes.
- [x] If my change requires a change to the documentation, I have updated it accordingly.

If you're unsure about any of these, don't hesitate to ask. We're here to help!


Patch:
diff --git a/mollie/invoices.go b/mollie/invoices.go
new file mode 100644
index 0000000..26b6fbc
--- /dev/null
+++ b/mollie/invoices.go
@@ -0,0 +1,111 @@
+package mollie
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/google/go-querystring/query"
+	"net/http"
+)
+
+// InvoiceStatus status of the invoice
+type InvoiceStatus string
+
+// Valid status of the invoice
+const (
+	InvoiceStatusOpen    InvoiceStatus = "open"
+	InvoiceStatusPaid    InvoiceStatus = "paid"
+	InvoiceStatusOverdue InvoiceStatus = "overdue"
+)
+
+// Invoice describes a invoice detail
+type Invoice struct {
+	Resource    string        `json:"resource,omitempty"`
+	ID          string        `json:"id,omitempty"`
+	Reference   string        `json:"reference,omitempty"`
+	VatNumber   string        `json:"vatNumber,omitempty"`
+	Status      InvoiceStatus `json:"status,omitempty"`
+	IssuedAt    string        `json:"issuedAt,omitempty"`
+	PaidAt      string        `json:"paidAt,omitempty"`
+	DueAt       string        `json:"dueAt,omitempty"`
+	NetAmount   Amount        `json:"netAmount,omitempty"`
+	VatAmount   Amount        `json:"vatAmount,omitempty"`
+	GrossAmount Amount        `json:"grossAmount,omitempty"`
+	Lines       []*LineItem   `json:"lines,omitempty"`
+	Links       *InvoiceLinks `json:"_links,omitempty"`
+}
+
+// LineItem product detail
+type LineItem struct {
+	Period        string  `json:"period,omitempty"`
+	Description   string  `json:"description,omitempty"`
+	Count         int64   `json:"count,omitempty"`
+	VatPercentage float64 `json:"vatPercentage,omitempty"`
+	Amount        Amount  `json:"amount,omitempty"`
+}
+
+// InvoiceLinks describes all the possible links to be returned with
+// a invoice object.
+type InvoiceLinks struct {
+	Self          URL `json:"self,omitempty"`
+	PDF           URL `json:"pdf,omitempty"`
+	Documentation URL `json:"documentation,omitempty"`
+}
+
+// ListInvoiceOptions describes list invoices endpoint valid query string parameters.
+type ListInvoiceOptions struct {
+	Reference string `json:"reference,omitempty"`
+	Year      string `json:"year,omitempty"`
+	From      int64  `json:"from,omitempty"`
+	Limit     int64  `json:"limit,omitempty"`
+}
+
+// InvoiceList describes how a list of invoices will be retrieved by Mollie.
+type InvoiceList struct {
+	Count    int `json:"count,omitempty"`
+	Embedded struct {
+		Invoices []Invoice `json:"invoices"`
+	} `json:"_embedded,omitempty"`
+	Links PaginationLinks `json:"_links,omitempty"`
+}
+
+// InvoicesService instance operates over invoice resources
+type InvoicesService service
+
+// Get retrieve details of an invoice, using the invoice’s identifier.
+func (is *InvoicesService) Get(id string) (i Invoice, err error) {
+	getURL := fmt.Sprintf("v2/invoices/%s", id)
+
+	req, err := is.client.NewAPIRequest(http.MethodGet, getURL, nil)
+	if err != nil {
+		return
+	}
+	res, err := is.client.Do(req)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(res.content, &i); err != nil {
+		return
+	}
+	return
+}
+
+// List retrieves a list of invoices associated with your account/organization.
+func (is *InvoicesService) List(options *ListInvoiceOptions) (il InvoiceList, err error) {
+	u := fmt.Sprint("v2/invoices")
+	if options != nil {
+		v, _ := query.Values(options)
+		u = fmt.Sprintf("%s?%s", u, v.Encode())
+	}
+	req, err := is.client.NewAPIRequest(http.MethodGet, u, nil)
+	if err != nil {
+		return
+	}
+	res, err := is.client.Do(req)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(res.content, &il); err != nil {
+		return
+	}
+	return
+}
diff --git a/mollie/mollie.go b/mollie/mollie.go
index 6278e19..2c307a4 100644
--- a/mollie/mollie.go
+++ b/mollie/mollie.go
@@ -39,6 +39,7 @@ type Client struct {
 	Payments    *PaymentsService
 	Chargebacks *ChargebacksService
 	Methods     *MethodsService
+	Invoices    *InvoicesService
 }
 
 type service struct {
@@ -150,6 +151,7 @@ func NewClient(baseClient *http.Client, c *Config) (mollie *Client, err error) {
 	mollie.Payments = (*PaymentsService)(&mollie.common)
 	mollie.Chargebacks = (*ChargebacksService)(&mollie.common)
 	mollie.Methods = (*MethodsService)(&mollie.common)
+	mollie.Invoices = (*InvoicesService)(&mollie.common)
 
 	// Parse authorization from environment
 	if tkn, ok := os.LookupEnv(APITokenEnv); ok {


Test patch:
diff --git a/mollie/invoices_test.go b/mollie/invoices_test.go
new file mode 100644
index 0000000..ef135f9
--- /dev/null
+++ b/mollie/invoices_test.go
@@ -0,0 +1,91 @@
+package mollie
+
+import (
+	"github.com/VictorAvelar/mollie-api-go/testdata"
+	"net/http"
+	"testing"
+)
+
+func TestInvoicesService_Get(t *testing.T) {
+	setup()
+	defer teardown()
+	id := "inv_xBEbP9rvAq"
+	_ = tClient.WithAuthenticationValue("test_token")
+	tMux.HandleFunc("/v2/invoices/"+id, func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, "GET")
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(testdata.GetInvoiceResponse))
+	})
+
+	res, err := tClient.Invoices.Get(id)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if res.ID != id {
+		t.Errorf("mismatching info. want %v, got %v", id, res.ID)
+	}
+}
+
+func TestInvoicesService_List(t *testing.T) {
+	setup()
+	defer teardown()
+	_ = tClient.WithAuthenticationValue("test_token")
+	tMux.HandleFunc("/v2/invoices", func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, "GET")
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(testdata.ListInvoicesResponse))
+	})
+
+	res, err := tClient.Invoices.List(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if res.Count == 0 {
+		t.Errorf("mismatching info. want %v, got %v", 0, res.Count)
+	}
+}
+
+func TestInvoicesService_ListWithOptions(t *testing.T) {
+	setup()
+	defer teardown()
+	_ = tClient.WithAuthenticationValue("test_token")
+	tMux.HandleFunc("/v2/invoices", func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, "GET")
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(testdata.ListInvoicesResponse))
+	})
+
+	options := &ListInvoiceOptions{
+		Reference: "2016.10000",
+	}
+
+	res, err := tClient.Invoices.List(options)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if res.Count == 0 {
+		t.Errorf("mismatching info. want %v, got %v", 0, res.Count)
+	}
+
+	if res.Embedded.Invoices[0].Reference != "2016.10000" {
+		t.Errorf("mismatching info. want %v, got %v", 0, res.Embedded.Invoices[0].Reference)
+	}
+}
diff --git a/testdata/invoices.go b/testdata/invoices.go
new file mode 100644
index 0000000..2c0ccc1
--- /dev/null
+++ b/testdata/invoices.go
@@ -0,0 +1,115 @@
+package testdata
+
+// GetInvoiceResponse example
+const GetInvoiceResponse = `{
+    "resource": "invoice",
+    "id": "inv_xBEbP9rvAq",
+    "reference": "2016.10000",
+    "vatNumber": "NL001234567B01",
+    "status": "open",
+    "issuedAt": "2016-08-31",
+    "dueAt": "2016-09-14",
+    "netAmount": {
+        "value": "45.00",
+        "currency": "EUR"
+    },
+    "vatAmount": {
+        "value": "9.45",
+        "currency": "EUR"
+    },
+    "grossAmount": {
+        "value": "54.45",
+        "currency": "EUR"
+    },
+    "lines":[
+        {
+            "period": "2016-09",
+            "description": "iDEAL transactiekosten",
+            "count": 100,
+            "vatPercentage": 21,
+            "amount": {
+                "value": "45.00",
+                "currency": "EUR"
+            }
+        }
+    ],
+    "_links": {
+        "self": {
+             "href": "https://api.mollie.com/v2/invoices/inv_xBEbP9rvAq",
+             "type": "application/hal+json"
+        },
+        "pdf": {
+             "href": "https://www.mollie.com/merchant/download/invoice/xBEbP9rvAq/2ab44d60b35b1d06090bba955fa2c602",
+             "type": "application/pdf",
+             "expiresAt": "2018-11-09T14:10:36+00:00"
+        }
+    }
+}`
+
+// ListInvoicesResponse example
+const ListInvoicesResponse = `{
+    "count": 5,
+    "_embedded": {
+        "invoices": [
+            {
+                "resource": "invoice",
+                "id": "inv_xBEbP9rvAq",
+                "reference": "2016.10000",
+                "vatNumber": "NL001234567B01",
+                "status": "open",
+                "issuedAt": "2016-08-31",
+                "dueAt": "2016-09-14",
+                "netAmount": {
+                    "value": "45.00",
+                    "currency": "EUR"
+                },
+                "vatAmount": {
+                    "value": "9.45",
+                    "currency": "EUR"
+                },
+                "grossAmount": {
+                    "value": "54.45",
+                    "currency": "EUR"
+                },
+                "lines":[
+                    {
+                        "period": "2016-09",
+                        "description": "iDEAL transactiekosten",
+                        "count": 100,
+                        "vatPercentage": 21,
+                        "amount": {
+                            "value": "45.00",
+                            "currency": "EUR"
+                        }
+                    }
+                ],
+                "_links": {
+                    "self": {
+                         "href": "https://api.mollie.com/v2/invoices/inv_xBEbP9rvAq",
+                         "type": "application/hal+json"
+                    },
+                    "pdf": {
+                         "href": "https://www.mollie.com/merchant/download/invoice/xBEbP9rvAq/2ab44d60b35955fa2c602",
+                         "type": "application/pdf",
+                         "expiresAt": "2018-11-09T14:10:36+00:00"
+                    }
+                }
+            }
+        ]
+    },
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.nl/v2/invoices?limit=5",
+            "type": "application/hal+json"
+        },
+        "previous": null,
+        "next": {
+            "href": "https://api.mollie.nl/v2/invoices?from=inv_xBEbP9rvAq&limit=5",
+            "type": "application/hal+json"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/invoices-api/list-invoices",
+            "type": "text/html"
+        }
+    }
+}`
diff --git a/testdata/methods.go b/testdata/methods.go
index 40e75f7..f1ea3c0 100644
--- a/testdata/methods.go
+++ b/testdata/methods.go
@@ -1,7 +1,7 @@
 package testdata
 
 // ListMethodsResponse example
-const ListMethodsResponse = `{
+var ListMethodsResponse = `{
     "count": 13,
     "_embedded": {
         "methods": [
@@ -104,7 +104,7 @@ const ListMethodsResponse = `{
 }`
 
 // GetMethodResponse example
-const GetMethodResponse = `{
+var GetMethodResponse = `{
      "resource": "method",
      "id": "ideal",
      "description": "iDEAL",
diff --git a/testdata/payments.go b/testdata/payments.go
index 4863193..4908f7b 100644
--- a/testdata/payments.go
+++ b/testdata/payments.go
@@ -1,7 +1,7 @@
 package testdata
 
 // GetPaymentResponse example
-const GetPaymentResponse = `{
+var GetPaymentResponse = `{
     "resource": "payment",
     "id": "tr_WDqYK6vllg",
     "mode": "test",
@@ -40,7 +40,7 @@ const GetPaymentResponse = `{
 }`
 
 // CancelPaymentResponse example
-const CancelPaymentResponse = `{
+var CancelPaymentResponse = `{
     "resource": "payment",
     "id": "tr_WDqYK6vllg",
     "mode": "live",
@@ -76,7 +76,7 @@ const CancelPaymentResponse = `{
 }`
 
 // UpdatePaymentResponse example
-const UpdatePaymentResponse = `{
+var UpdatePaymentResponse = `{
     "resource": "payment",
     "id": "tr_7UhSN1zuXS",
     "mode": "test",
@@ -116,7 +116,7 @@ const UpdatePaymentResponse = `{
 `
 
 // ListPaymentsResponse example
-const ListPaymentsResponse = `{
+var ListPaymentsResponse = `{
     "count": 5,
     "_embedded": {
         "payments": [