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

Issue:
Implement Captures API resource
**Description**
Implement the API endpoints related to the captures 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/captures-api/get-capture


Pull request description:
Implement settlements api resource
<!--- Provide a general summary of your changes in the Title above -->

## Description
- Implementation of Settlements API resource
- Implementation of Captures API resource

## Motivation and context

Fixes #11 and #26 

## How has this been tested?

Unit test coverage

## 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/captures.go b/mollie/captures.go
new file mode 100644
index 0000000..f0f3815
--- /dev/null
+++ b/mollie/captures.go
@@ -0,0 +1,87 @@
+package mollie
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"time"
+)
+
+// CapturesService operates over captures resource
+type CapturesService service
+
+// CaptureLinks contains relevant links for a capture object
+type CaptureLinks struct {
+	Self          URL `json:"self,omitempty"`
+	Payment       URL `json:"payment,omitempty"`
+	Shipment      URL `json:"shipment,omitempty"`
+	Settlement    URL `json:"settlement,omitempty"`
+	Documentation URL `json:"documentation,omitempty"`
+}
+
+// Capture describes a single capture
+// Captures are used for payments that have the authorize-then-capture flow
+type Capture struct {
+	Resource         string       `json:"resource,omitempty"`
+	ID               string       `json:"id,omitempty"`
+	Mode             Mode         `json:"mode,omitempty"`
+	Amount           Amount       `json:"amount,omitempty"`
+	SettlementAmount Amount       `json:"settlementAmount,omitempty"`
+	PaymentID        string       `json:"paymentId,omitempty"`
+	ShipmentID       string       `json:"shipmentId,omitempty"`
+	SettlementID     string       `json:"settlementId,omitempty"`
+	CreatedAt        *time.Time   `json:"createdAt,omitempty"`
+	Links            CaptureLinks `json:"links,omitempty"`
+}
+
+// CapturesList describes a list of captures
+type CapturesList struct {
+	Count    int `json:"count,omitempty"`
+	Embedded struct {
+		Captures []Capture
+	} `json:"_embedded,omitempty"`
+	Links PaginationLinks `json:"_links,omitempty"`
+}
+
+// Get retrieves a single capture by its ID.
+// Note the original payment’s ID is needed as well.
+//
+// See: https://docs.mollie.com/reference/v2/captures-api/get-capture
+func (cs *CapturesService) Get(pID, cID string) (c *Capture, err error) {
+	u := fmt.Sprintf("v2/payments/%s/captures/%s", pID, cID)
+	req, err := cs.client.NewAPIRequest(http.MethodGet, u, nil)
+	if err != nil {
+		return
+	}
+
+	res, err := cs.client.Do(req)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &c); err != nil {
+		return
+	}
+	return
+}
+
+// List retrieves all captures for a certain payment
+//
+// See: https://docs.mollie.com/reference/v2/captures-api/list-captures
+func (cs *CapturesService) List(pID string) (cl *CapturesList, err error) {
+	u := fmt.Sprintf("v2/payments/%s/captures", pID)
+	req, err := cs.client.NewAPIRequest(http.MethodGet, u, nil)
+	if err != nil {
+		return
+	}
+
+	res, err := cs.client.Do(req)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &cl); err != nil {
+		return
+	}
+	return
+}
diff --git a/mollie/common_types.go b/mollie/common_types.go
index 7327f6c..6a52874 100644
--- a/mollie/common_types.go
+++ b/mollie/common_types.go
@@ -154,3 +154,9 @@ type ErrorResponse struct {
 		Documentation URL `json:"documentation,omitempty"`
 	} `json:"_links,omitempty"`
 }
+
+// Rate describes service rates, further divided into fixed and percentage costs.
+type Rate struct {
+	Fixed    Amount `json:"fixed,omitempty"`
+	Variable string `json:"variable,omitempty"`
+}
diff --git a/mollie/mollie.go b/mollie/mollie.go
index 412282f..ebb765c 100644
--- a/mollie/mollie.go
+++ b/mollie/mollie.go
@@ -45,6 +45,8 @@ type Client struct {
 	Refunds       *RefundsService
 	Shipments     *ShipmentsService
 	Orders        *OrdersService
+	Settlements   *SettlementsService
+	Captures      *CapturesService
 }
 
 type service struct {
@@ -162,6 +164,8 @@ func NewClient(baseClient *http.Client, c *Config) (mollie *Client, err error) {
 	mollie.Refunds = (*RefundsService)(&mollie.common)
 	mollie.Shipments = (*ShipmentsService)(&mollie.common)
 	mollie.Orders = (*OrdersService)(&mollie.common)
+	mollie.Captures = (*CapturesService)(&mollie.common)
+	mollie.Settlements = (*SettlementsService)(&mollie.common)
 
 	// Parse authorization from environment
 	if tkn, ok := os.LookupEnv(APITokenEnv); ok {
diff --git a/mollie/settlements.go b/mollie/settlements.go
new file mode 100644
index 0000000..f34d5c5
--- /dev/null
+++ b/mollie/settlements.go
@@ -0,0 +1,239 @@
+package mollie
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/google/go-querystring/query"
+)
+
+// SettlementsService operates over settlements resource
+type SettlementsService service
+
+// SettlementStatus describes the status of the settlement
+type SettlementStatus string
+
+// Available settlement statuses
+const (
+	SettlementStatusOpen    = "string"
+	SettlementStatusPending = "pending"
+	SettlementStatusPaidOut = "paidout"
+	SettlementStatusFailed  = "failed"
+)
+
+// SettlementRevenue objects contain the total revenue for each payment method during this period
+type SettlementRevenue struct {
+	Description string        `json:"description,omitempty"`
+	AmountNet   Amount        `json:"amountNet,omitempty"`
+	AmountVAT   Amount        `json:"amountVat,omitempty"`
+	AmountGross Amount        `json:"amountGross,omitempty"`
+	Count       int           `json:"count,omitempty"`
+	Method      PaymentMethod `json:"method,omitempty"`
+}
+
+// SettlementCosts contains information about costs related to a settlement
+type SettlementCosts struct {
+	Description string        `json:"description,omitempty"`
+	AmountNet   Amount        `json:"amountNet,omitempty"`
+	AmountVAT   Amount        `json:"amountVat,omitempty"`
+	AmountGross Amount        `json:"amountGross,omitempty"`
+	Count       int           `json:"count,omitempty"`
+	Rate        Rate          `json:"rate,omitempty"`
+	Method      PaymentMethod `json:"method,omitempty"`
+}
+
+// SettlementPeriod describe the settlement by month in full detail
+type SettlementPeriod struct {
+	Revenue []SettlementRevenue `json:"revenue,omitempty"`
+	Costs   []SettlementCosts   `json:"costs,omitempty"`
+}
+
+// SettlementObject nests as describes for settlement periods
+type SettlementObject map[string]map[string]SettlementPeriod
+
+// SettlementLinks is an object with several URL objects relevant to the settlement
+type SettlementLinks struct {
+	Self          URL `json:"self,omitempty"`
+	Payments      URL `json:"payments,omitempty"`
+	Refunds       URL `json:"refunds,omitempty"`
+	Chargebacks   URL `json:"chargebacks,omitempty"`
+	Captures      URL `json:"captures,omitempty"`
+	Invoice       URL `json:"invoice,omitempty"`
+	Documentation URL `json:"documentation,omitempty"`
+}
+
+// Settlement contains successful payments, together with refunds,
+// captures and chargebacks into settlements
+type Settlement struct {
+	ID        string           `json:"id,omitempty"`
+	Resource  string           `json:"resource,omitempty"`
+	Reference string           `json:"reference,omitempty"`
+	CreatedAt *time.Time       `json:"createdAt,omitempty"`
+	SettledAt *time.Time       `json:"settledAt,omitempty"`
+	Status    SettlementStatus `json:"status,omitempty"`
+	Amount    Amount           `json:"amount,omitempty"`
+	Periods   SettlementObject `json:"periods,omitempty"`
+	InvoiceID string           `json:"invoiceId,omitempty"`
+	Links     SettlementLinks  `json:"_links,omitempty"`
+}
+
+// SettlementsListOptions contains query parameters for settlement lists.
+type SettlementsListOptions struct {
+	From  *ShortDate `url:"from,omitempty"`
+	Limit int        `url:"limit,omitempty"`
+	Embed EmbedValue `url:"embed,omitempty"`
+}
+
+// SettlementsList describes a list of settlements.
+type SettlementsList struct {
+	Count    int `json:"count,omitempty"`
+	Embedded struct {
+		Settlements []Settlement
+	} `json:"_embedded,omitempty"`
+	Links PaginationLinks `json:"_links,omitempty"`
+}
+
+// Get returns a settlement by its id or the bank reference id
+//
+// See: https://docs.mollie.com/reference/v2/settlements-api/get-settlement
+func (ss *SettlementsService) Get(id string) (s *Settlement, err error) {
+	return ss.get(id)
+}
+
+// Next retrieves the details of the current settlement that has not yet been paid out.
+//
+// See: https://docs.mollie.com/reference/v2/settlements-api/get-next-settlement
+func (ss *SettlementsService) Next() (s *Settlement, err error) {
+	return ss.get("next")
+}
+
+// Open retrieves the details of the open balance of the organization.
+// This will return a settlement object representing your organization’s balance.
+//
+// See: https://docs.mollie.com/reference/v2/settlements-api/get-open-settlement
+func (ss *SettlementsService) Open() (s *Settlement, err error) {
+	return ss.get("open")
+}
+
+// List retrieves all settlements, ordered from new to old
+//
+// See: https://docs.mollie.com/reference/v2/settlements-api/list-settlements
+func (ss *SettlementsService) List(slo *SettlementsListOptions) (sl *SettlementsList, err error) {
+	res, err := ss.list("", "", slo)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &sl); err != nil {
+		return
+	}
+	return
+}
+
+// GetPayments retrieves all payments included in a settlement.
+//
+// See: https://docs.mollie.com/reference/v2/settlements-api/list-settlement-payments
+func (ss *SettlementsService) GetPayments(id string, slo *SettlementsListOptions) (pl *PaymentList, err error) {
+	res, err := ss.list(id, "payments", slo)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &pl); err != nil {
+		return
+	}
+	return
+}
+
+// GetRefunds retrieves all refunds included in a settlement.
+//
+// See: https://docs.mollie.com/reference/v2/settlements-api/list-settlement-refunds
+func (ss *SettlementsService) GetRefunds(id string, slo *SettlementsListOptions) (rl *RefundList, err error) {
+	res, err := ss.list(id, "refunds", slo)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &rl); err != nil {
+		return
+	}
+	return
+}
+
+// GetChargebacks retrieves all chargebacks included in a settlement.
+//
+// See: https://docs.mollie.com/reference/v2/settlements-api/list-settlement-chargebacks
+func (ss *SettlementsService) GetChargebacks(id string, slo *SettlementsListOptions) (cl *ChargebackList, err error) {
+	res, err := ss.list(id, "chargebacks", slo)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &cl); err != nil {
+		return
+	}
+	return
+}
+
+// GetCaptures retrieves all captures included in a settlement.
+//
+// See: https://docs.mollie.com/reference/v2/settlements-api/list-settlement-captures
+func (ss *SettlementsService) GetCaptures(id string, slo *SettlementsListOptions) (cl *CapturesList, err error) {
+	res, err := ss.list(id, "captures", slo)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &cl); err != nil {
+		return
+	}
+	return
+}
+
+func (ss *SettlementsService) get(element string) (s *Settlement, err error) {
+	u := fmt.Sprintf("v2/settlements/%s", element)
+	req, err := ss.client.NewAPIRequest(http.MethodGet, u, nil)
+	if err != nil {
+		return
+	}
+
+	res, err := ss.client.Do(req)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &s); err != nil {
+		return
+	}
+	return
+}
+
+func (ss *SettlementsService) list(id string, category string, slo *SettlementsListOptions) (res *Response, err error) {
+	u := "v2/settlements"
+
+	if id != "" {
+		u = fmt.Sprintf("%s/%s", u, id)
+
+		if category != "" {
+			u = fmt.Sprintf("%s/%s", u, category)
+		}
+	}
+
+	if slo != nil {
+		v, _ := query.Values(slo)
+		u = fmt.Sprintf("%s?%s", u, v.Encode())
+	}
+
+	req, err := ss.client.NewAPIRequest(http.MethodGet, u, nil)
+	if err != nil {
+		return
+	}
+
+	res, err = ss.client.Do(req)
+	if err != nil {
+		return
+	}
+	return
+}


Test patch:
diff --git a/mollie/captures_test.go b/mollie/captures_test.go
new file mode 100644
index 0000000..2ef92f1
--- /dev/null
+++ b/mollie/captures_test.go
@@ -0,0 +1,126 @@
+package mollie
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	"github.com/VictorAvelar/mollie-api-go/testdata"
+)
+
+func TestCapturesService_Get(t *testing.T) {
+	setup()
+	defer teardown()
+
+	pID := "tr_WDqYK6vllg"
+	cID := "cpt_4qqhO89gsT"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc(fmt.Sprintf("/v2/payments/%s/captures/%s", pID, cID), 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.GetCaptureResponse))
+	})
+
+	capture, err := tClient.Captures.Get(pID, cID)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if capture.ID != cID {
+		t.Errorf("unexpected response: got %v, want %v", capture.ID, cID)
+	}
+}
+
+func TestCapturesService_List(t *testing.T) {
+	setup()
+	defer teardown()
+
+	pID := "tr_WDqYK6vllg"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc(fmt.Sprintf("/v2/payments/%s/captures", pID), 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.ListCapturesResponse))
+	})
+
+	captures, err := tClient.Captures.List(pID)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if len(captures.Embedded.Captures) < 1 {
+		t.Errorf("empty response with %d captures, expecting 1", len(captures.Embedded.Captures))
+	}
+}
+
+func TestCapturesService_HTTPRequestErrors(t *testing.T) {
+	setup()
+	defer teardown()
+
+	tMux.HandleFunc("/v2/payments/09d87sd8a9d/captures/", errorHandler)
+
+	tests := forceCapturesErrors()
+
+	for _, tt := range tests {
+		if !strings.Contains(tt.Error(), "Internal Server Error") {
+			t.Error(tt)
+		}
+	}
+}
+
+func TestCapturesService_NewAPIRequestErrors(t *testing.T) {
+	setup()
+	defer teardown()
+	u, _ := url.Parse(tServer.URL)
+	tClient.BaseURL = u
+
+	tMux.HandleFunc("/v2/payments/09d87sd8a9d/captures/", errorHandler)
+
+	tests := forceCapturesErrors()
+
+	for _, tt := range tests {
+		if tt != errBadBaseURL {
+			t.Error(tt)
+		}
+	}
+}
+
+func TestCapturesService_JsonEncodingErrors(t *testing.T) {
+	setup()
+	defer teardown()
+
+	tMux.HandleFunc("/v2/payments/09d87sd8a9d/captures/", encodingHandler)
+
+	tests := forceCapturesErrors()
+
+	for _, tt := range tests {
+		if tt == nil {
+			t.Error(tt)
+		} else if !strings.Contains(tt.Error(), "invalid character") {
+			t.Errorf("unexpected error %v", tt)
+		}
+	}
+}
+
+func forceCapturesErrors() []error {
+	_, lerr := tClient.Captures.List("09d87sd8a9d")
+	_, gerr := tClient.Captures.Get("09d87sd8a9d", "4asd5ad6")
+
+	return []error{lerr, gerr}
+}
diff --git a/mollie/settlements_test.go b/mollie/settlements_test.go
new file mode 100644
index 0000000..de76e79
--- /dev/null
+++ b/mollie/settlements_test.go
@@ -0,0 +1,293 @@
+package mollie
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	"github.com/VictorAvelar/mollie-api-go/testdata"
+)
+
+func TestSettlementsService_Get(t *testing.T) {
+	setup()
+	defer teardown()
+
+	cID := "stl_jDk30akdN"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc(fmt.Sprintf("/v2/settlements/%s", cID), 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.GetSettlementsResponse))
+	})
+
+	settlement, err := tClient.Settlements.Get(cID)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if settlement.ID != cID {
+		t.Errorf("unexpected response: got: %v, want %v", settlement.ID, cID)
+	}
+}
+
+func TestSettlementsService_Next(t *testing.T) {
+	setup()
+	defer teardown()
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/settlements/next", 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.GetNextSettlementResponse))
+	})
+
+	settlement, err := tClient.Settlements.Next()
+	if err != nil {
+		t.Error(err)
+	}
+
+	if settlement.ID != "next" {
+		t.Errorf("unexpected response: got: %v, want 'next'", settlement.ID)
+	}
+}
+
+func TestSettlementsService_Open(t *testing.T) {
+	setup()
+	defer teardown()
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/settlements/open", 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.GetOpenSettlementResponse))
+	})
+
+	settlement, err := tClient.Settlements.Open()
+	if err != nil {
+		t.Error(err)
+	}
+
+	if settlement.ID != "open" {
+		t.Errorf("unexpected response: got: %v, want 'open'", settlement.ID)
+	}
+}
+
+func TestSettlementsService_List(t *testing.T) {
+	setup()
+	defer teardown()
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/settlements", 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.ListSettlementsResponse))
+	})
+
+	settlement, err := tClient.Settlements.List(&SettlementsListOptions{
+		Limit: 10,
+	})
+	if err != nil {
+		t.Error(err)
+	}
+
+	if settlement.Count != 5 {
+		t.Errorf("unexpected response: got: %v, want 5", settlement.Count)
+	}
+}
+
+func TestSettlementsService_GetPayments(t *testing.T) {
+	setup()
+	defer teardown()
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/settlements/stl_jDk30akdN/payments", 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.ListPaymentsResponse))
+	})
+
+	list, err := tClient.Settlements.GetPayments("stl_jDk30akdN", nil)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if list.Count != 5 {
+		t.Errorf("unexpected response: got: %v, want 5", list.Count)
+	}
+}
+
+func TestSettlementsService_GetCaptures(t *testing.T) {
+	setup()
+	defer teardown()
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/settlements/stl_jDk30akdN/captures", 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.ListCapturesResponse))
+	})
+
+	list, err := tClient.Settlements.GetCaptures("stl_jDk30akdN", nil)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if list.Count != 1 {
+		t.Errorf("unexpected response: got: %v, want 1", list.Count)
+	}
+}
+
+func TestSettlementsService_GetChargebacks(t *testing.T) {
+	setup()
+	defer teardown()
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/settlements/stl_jDk30akdN/chargebacks", 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.ListChargebacksResponse))
+	})
+
+	list, err := tClient.Settlements.GetChargebacks("stl_jDk30akdN", nil)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if list.Count != 3 {
+		t.Errorf("unexpected response: got: %v, want 3", list.Count)
+	}
+}
+
+func TestSettlementsService_GetRefunds(t *testing.T) {
+	setup()
+	defer teardown()
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/settlements/stl_jDk30akdN/refunds", 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.ListOrderRefundResponse))
+	})
+
+	list, err := tClient.Settlements.GetRefunds("stl_jDk30akdN", nil)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if list.Count != 1 {
+		t.Errorf("unexpected response: got: %v, want 1", list.Count)
+	}
+}
+
+func TestSettlementsService_HTTPRequestErrors(t *testing.T) {
+	setup()
+	defer teardown()
+
+	tMux.HandleFunc("/v2/settlements/", errorHandler)
+
+	tests := forceSettlementsErrors()
+
+	for _, tt := range tests {
+		if !strings.Contains(tt.Error(), "Internal Server Error") {
+			t.Error(tt)
+		}
+	}
+}
+
+func TestSettlementsService_NewAPIRequestErrors(t *testing.T) {
+	setup()
+	defer teardown()
+	u, _ := url.Parse(tServer.URL)
+	tClient.BaseURL = u
+
+	tMux.HandleFunc("/v2/settlements/", errorHandler)
+
+	tests := forceSettlementsErrors()
+
+	for _, tt := range tests {
+		if tt != errBadBaseURL {
+			t.Error(tt)
+		}
+	}
+}
+
+func TestSettlementsService_JsonEncodingErrors(t *testing.T) {
+	setup()
+	defer teardown()
+
+	tMux.HandleFunc("/v2/settlements/", encodingHandler)
+
+	tests := forceSettlementsErrors()
+
+	for _, tt := range tests {
+		if tt == nil {
+			t.Error(tt)
+		} else if !strings.Contains(tt.Error(), "invalid character") {
+			t.Errorf("unexpected error %v", tt)
+		}
+	}
+}
+
+func forceSettlementsErrors() []error {
+	_, gerr := tClient.Settlements.Get("10298372891")
+	_, nerr := tClient.Settlements.Next()
+	_, oerr := tClient.Settlements.Open()
+	_, lerr := tClient.Settlements.List(nil)
+	_, perr := tClient.Settlements.GetPayments("stl_jDk30akdN", nil)
+	_, cerr := tClient.Settlements.GetCaptures("stl_jDk30akdN", nil)
+	_, cberr := tClient.Settlements.GetChargebacks("stl_jDk30akdN", nil)
+	_, rerr := tClient.Settlements.GetRefunds("stl_jDk30akdN", nil)
+
+	return []error{gerr, nerr, oerr, lerr, perr, cerr, cberr, rerr}
+}
diff --git a/testdata/captures.go b/testdata/captures.go
new file mode 100644
index 0000000..616a605
--- /dev/null
+++ b/testdata/captures.go
@@ -0,0 +1,102 @@
+package testdata
+
+// ListCapturesResponse example
+const ListCapturesResponse = `{
+    "_embedded": {
+        "captures": [
+            {
+                "resource": "capture",
+                "id": "cpt_4qqhO89gsT",
+                "mode": "live",
+                "amount": {
+                    "value": "1027.99",
+                    "currency": "EUR"
+                },
+                "settlementAmount": {
+                    "value": "399.00",
+                    "currency": "EUR"
+                },
+                "paymentId": "tr_WDqYK6vllg",
+                "shipmentId": "shp_3wmsgCJN4U",
+                "settlementId": "stl_jDk30akdN",
+                "createdAt": "2018-08-02T09:29:56+00:00",
+                "_links": {
+                    "self": {
+                        "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/captures/cpt_4qqhO89gsT",
+                        "type": "application/hal+json"
+                    },
+                    "payment": {
+                        "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg",
+                        "type": "application/hal+json"
+                    },
+                    "shipment": {
+                        "href": "https://api.mollie.com/v2/orders/ord_8wmqcHMN4U/shipments/shp_3wmsgCJN4U",
+                        "type": "application/hal+json"
+                    },
+                    "settlement": {
+                        "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN",
+                        "type": "application/hal+json"
+                    },
+                    "documentation": {
+                        "href": "https://docs.mollie.com/reference/v2/captures-api/get-capture",
+                        "type": "text/html"
+                    }
+                }
+            }
+        ]
+    },
+    "count": 1,
+    "_links": {
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/captures-api/list-captures",
+            "type": "text/html"
+        },
+        "self": {
+            "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/captures?limit=50",
+            "type": "application/hal+json"
+        },
+        "previous": null,
+        "next": null
+    }
+}`
+
+// GetCaptureResponse example
+const GetCaptureResponse = `{
+    "resource": "capture",
+    "id": "cpt_4qqhO89gsT",
+    "mode": "live",
+    "amount": {
+        "value": "1027.99",
+        "currency": "EUR"
+    },
+    "settlementAmount": {
+        "value": "399.00",
+        "currency": "EUR"
+    },
+    "paymentId": "tr_WDqYK6vllg",
+    "shipmentId": "shp_3wmsgCJN4U",
+    "settlementId": "stl_jDk30akdN",
+    "createdAt": "2018-08-02T09:29:56+00:00",
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/captures/cpt_4qqhO89gsT",
+            "type": "application/hal+json"
+        },
+        "payment": {
+            "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg",
+            "type": "application/hal+json"
+        },
+        "shipment": {
+            "href": "https://api.mollie.com/v2/orders/ord_8wmqcHMN4U/shipments/shp_3wmsgCJN4U",
+            "type": "application/hal+json"
+        },
+        "settlement": {
+            "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN",
+            "type": "application/hal+json"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/captures-api/get-capture",
+            "type": "text/html"
+        }
+    }
+}`
diff --git a/testdata/settlements.go b/testdata/settlements.go
new file mode 100644
index 0000000..9b88287
--- /dev/null
+++ b/testdata/settlements.go
@@ -0,0 +1,422 @@
+package testdata
+
+// GetSettlementsResponse example
+const GetSettlementsResponse = `{
+    "resource": "settlement",
+    "id": "stl_jDk30akdN",
+    "reference": "1234567.1804.03",
+    "createdAt": "2018-04-06T06:00:01.0Z",
+    "settledAt": "2018-04-06T09:41:44.0Z",
+    "status": "paidout",
+    "amount": {
+        "currency": "EUR",
+        "value": "39.75"
+    },
+    "periods": {
+        "2018": {
+            "4": {
+                "revenue": [
+                    {
+                        "description": "iDEAL",
+                        "method": "ideal",
+                        "count": 6,
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "86.1000"
+                        },
+                        "amountVat": null,
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "86.1000"
+                        }
+                    },
+                    {
+                        "description": "Refunds iDEAL",
+                        "method": "refund",
+                        "count": 2,
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "-43.2000"
+                        },
+                        "amountVat": null,
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "43.2000"
+                        }
+                    }
+                ],
+                "costs": [
+                    {
+                        "description": "iDEAL",
+                        "method": "ideal",
+                        "count": 6,
+                        "rate": {
+                            "fixed": {
+                                "currency": "EUR",
+                                "value": "0.3500"
+                            },
+                            "percentage": null
+                        },
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "2.1000"
+                        },
+                        "amountVat": {
+                            "currency": "EUR",
+                            "value": "0.4410"
+                        },
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "2.5410"
+                        }
+                    },
+                    {
+                        "description": "Refunds iDEAL",
+                        "method": "refund",
+                        "count": 2,
+                        "rate": {
+                            "fixed": {
+                                "currency": "EUR",
+                                "value": "0.2500"
+                            },
+                            "percentage": null
+                        },
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "0.5000"
+                        },
+                        "amountVat": {
+                            "currency": "EUR",
+                            "value": "0.1050"
+                        },
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "0.6050"
+                        }
+                    }
+                ]
+            }
+        }
+    },
+    "invoiceId": "inv_FrvewDA3Pr",
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/settlements/next",
+            "type": "application/hal+json"
+        },
+        "payments": {
+            "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/payments",
+            "type": "application/hal+json"
+        },
+        "refunds": {
+            "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/refunds",
+            "type": "application/hal+json"
+        },
+        "chargebacks": {
+            "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/chargebacks",
+            "type": "application/hal+json"
+        },
+        "captures": {
+            "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/captures",
+            "type": "application/hal+json"
+        },
+        "invoice": {
+             "href": "https://api.mollie.com/v2/invoices/inv_FrvewDA3Pr",
+             "type": "application/hal+json"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/settlements-api/get-settlement",
+            "type": "text/html"
+        }
+    }
+}`
+
+// GetNextSettlementResponse example
+const GetNextSettlementResponse = `{
+    "resource": "settlement",
+    "id": "next",
+    "reference": null,
+    "createdAt": "2018-04-06T06:00:01.0Z",
+    "settledAt": null,
+    "status": "open",
+    "amount": {
+        "currency": "EUR",
+        "value": "39.75"
+    },
+    "periods": {
+        "2018": {
+            "4": {
+                "revenue": [
+                    {
+                        "description": "iDEAL",
+                        "method": "ideal",
+                        "count": 6,
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "86.1000"
+                        },
+                        "amountVat": null,
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "86.1000"
+                        }
+                    },
+                    {
+                        "description": "Refunds iDEAL",
+                        "method": "refund",
+                        "count": 2,
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "-43.2000"
+                        },
+                        "amountVat": null,
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "43.2000"
+                        }
+                    }
+                ],
+                "costs": [
+                    {
+                        "description": "iDEAL",
+                        "method": "ideal",
+                        "count": 6,
+                        "rate": {
+                            "fixed": {
+                                "currency": "EUR",
+                                "value": "0.3500"
+                            },
+                            "percentage": null
+                        },
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "2.1000"
+                        },
+                        "amountVat": {
+                            "currency": "EUR",
+                            "value": "0.4410"
+                        },
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "2.5410"
+                        }
+                    },
+                    {
+                        "description": "Refunds iDEAL",
+                        "method": "refund",
+                        "count": 2,
+                        "rate": {
+                            "fixed": {
+                                "currency": "EUR",
+                                "value": "0.2500"
+                            },
+                            "percentage": null
+                        },
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "0.5000"
+                        },
+                        "amountVat": {
+                            "currency": "EUR",
+                            "value": "0.1050"
+                        },
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "0.6050"
+                        }
+                    }
+                ]
+            }
+        }
+    },
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/settlements/next",
+            "type": "application/hal+json"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/settlements-api/get-next-settlement",
+            "type": "text/html"
+        }
+    }
+}`
+
+// GetOpenSettlementResponse example
+const GetOpenSettlementResponse = `{
+    "resource": "settlement",
+    "id": "open",
+    "reference": null,
+    "createdAt": "2018-04-06T06:00:01.0Z",
+    "settledAt": null,
+    "status": "open",
+    "amount": {
+        "currency": "EUR",
+        "value": "39.75"
+    },
+    "periods": {
+        "2018": {
+            "4": {
+                "revenue": [
+                    {
+                        "description": "iDEAL",
+                        "method": "ideal",
+                        "count": 6,
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "86.1000"
+                        },
+                        "amountVat": null,
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "86.1000"
+                        }
+                    },
+                    {
+                        "description": "Refunds iDEAL",
+                        "method": "refund",
+                        "count": 2,
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "-43.2000"
+                        },
+                        "amountVat": null,
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "43.2000"
+                        }
+                    }
+                ],
+                "costs": [
+                    {
+                        "description": "iDEAL",
+                        "method": "ideal",
+                        "count": 6,
+                        "rate": {
+                            "fixed": {
+                                "currency": "EUR",
+                                "value": "0.3500"
+                            },
+                            "percentage": null
+                        },
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "2.1000"
+                        },
+                        "amountVat": {
+                            "currency": "EUR",
+                            "value": "0.4410"
+                        },
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "2.5410"
+                        }
+                    },
+                    {
+                        "description": "Refunds iDEAL",
+                        "method": "refund",
+                        "count": 2,
+                        "rate": {
+                            "fixed": {
+                                "currency": "EUR",
+                                "value": "0.2500"
+                            },
+                            "percentage": null
+                        },
+                        "amountNet": {
+                            "currency": "EUR",
+                            "value": "0.5000"
+                        },
+                        "amountVat": {
+                            "currency": "EUR",
+                            "value": "0.1050"
+                        },
+                        "amountGross": {
+                            "currency": "EUR",
+                            "value": "0.6050"
+                        }
+                    }
+                ]
+            }
+        }
+    },
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/settlements/next",
+            "type": "application/hal+json"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/settlements-api/get-next-settlement",
+            "type": "text/html"
+        }
+    }
+}`
+
+const ListSettlementsResponse = `
+{
+    "count": 5,
+    "_embedded": {
+        "settlements": [
+            {
+                "resource": "settlement",
+                "id": "stl_jDk30akdN",
+                "reference": "1234567.1804.03",
+                "createdAt": "2018-04-06T06:00:01.0Z",
+                "settledAt": "2018-04-06T09:41:44.0Z",
+                "status": "paidout",
+                "amount": {
+                    "currency": "EUR",
+                    "value": "39.75"
+                },
+                "periods": {
+                    "2018": {
+                        "4": {
+                            "revenue": [ ],
+                            "costs": [ ]
+                        }
+                    }
+                },
+                "_links": {
+                    "self": {
+                        "href": "https://api.mollie.com/v2/settlements/next",
+                        "type": "application/hal+json"
+                    },
+                    "payments": {
+                        "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/payments",
+                        "type": "application/hal+json"
+                    },
+                    "refunds": {
+                        "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/refunds",
+                        "type": "application/hal+json"
+                    },
+                    "chargebacks": {
+                        "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/chargebacks",
+                        "type": "application/hal+json"
+                    },
+                    "captures": {
+                        "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/captures",
+                        "type": "application/hal+json"
+                    },
+                    "documentation": {
+                        "href": "https://docs.mollie.com/reference/v2/settlements-api/get-settlement",
+                        "type": "text/html"
+                    }
+                }
+            }
+        ]
+    },
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/settlements?limit=5",
+            "type": "application/hal+json"
+        },
+        "previous": null,
+        "next": {
+            "href": "https://api.mollie.com/v2/settlements?from=stl_QM8w7JDEhU&limit=5",
+            "type": "application/hal+json"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/settlements-api/list-settlements",
+            "type": "text/html"
+        }
+    }
+}`