Repository: VictorAvelar/mollie-api-go
Pull request #32 (2019-10-12 02:15:11)

Feature/implement refunds api
<!--- Provide a general summary of your changes in the Title above -->

## Description

Implement the refund API

## Motivation and context

Fixes [#9 ](https://github.com/VictorAvelar/mollie-api-go/issues/9)

## How has this been tested?

The test file is included in \mollie\refunds_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/docs/README.md b/docs/README.md
index 2b0828f..bc1ea8b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -188,9 +188,12 @@ type Client struct {
 	BaseURL *url.URL
 
   // Services
-	Payments    *PaymentsService
-	Chargebacks *ChargebacksService
-	Methods     *MethodsService
+	Payments    	*PaymentsService
+	Chargebacks 	*ChargebacksService
+	Methods     	*MethodsService
+	Invoices      	*InvoicesService
+	Organizations 	*OrganizationsService
+	Refunds       	*RefundsService
 }
 ```
 
@@ -846,3 +849,174 @@ type UsedGiftCard struct {
 ```
 
 UsedGiftCard describes a used gift card.
+
+#### type Refund
+
+```go
+type Refund struct {
+	Resource         string        `json:"resource,omitempty"`
+	ID               string        `json:"id,omitempty"`
+	Amount           *Amount       `json:"amount,omitempty"`
+	SettlementID     string        `json:"settlementId,omitempty"`
+	SettlementAmount *Amount       `json:"settlementAmount,omitempty"`
+	Description      string        `json:"description,omitempty"`
+	Metadata         interface{}   `json:"metadata,omitempty"`
+	Status           *RefundStatus `json:"status,omitempty"`
+	Lines            *OrderLines   `json:"lines,omitempty"`
+	PaymentID        string        `json:"paymentId,omitempty"`
+	OrderID          string        `json:"orderId,omitempty"`
+	CreatedAt        *time.Time    `json:"createdAt,omitempty"`
+	Links            *RefundLinks  `json:"_links,omitempty"`
+}
+```
+
+Refund describe a refund for a certain payment.
+
+#### type RefundList
+
+```go
+type RefundList struct {
+	Count    int `json:"count,omitempty"`
+	Embedded struct {
+		Refunds []Refund
+	} `json:"_embedded,omitempty"`
+	Links PaginationLinks `json:"_links,omitempty"`
+}
+```
+
+RefundList describes how a list of refunds will be retrieved by Mollie.
+
+#### type RefundStatus
+
+```go
+type RefundStatus string
+```
+
+RefundStatus describes the status of the refund.
+
+```go
+const (
+	Queued     RefundStatus = "queued"
+	Pending    RefundStatus = "pending"
+	Processing RefundStatus = "processing"
+	Refunded   RefundStatus = "refunded"
+	Failed     RefundStatus = "failed"
+)
+```
+
+Valid refund status.
+
+#### type OrderLines
+
+```go
+type OrderLines struct {
+	Quantity       int     `json:"quantity,omitempty"`
+	DiscountAmount *Amount `json:"discountAmount,omitempty"`
+	VatAmount      *Amount `json:"vatAmount,omitempty"`
+	TotalAmount    *Amount `json:"totalAmount,omitempty"`
+}
+```
+
+OrderLines describes an array of order line objects.
+
+#### type RefundLinks
+
+```go
+type RefundLinks struct {
+	Self       *URL `json:"self,omitempty"`
+	Payment    *URL `json:"payment,omitempty"`
+	Settlement *URL `json:"settlement,omitempty"`
+	Order      *URL `json:"order,omitempty"`
+}
+```
+
+RefundLinks describes all the possible links to be returned with a Refund object.
+
+#### type RefundOptions
+
+```go
+type RefundOptions struct {
+	Embed    EmbedValue `url:"embed,omitempty"`
+	TestMode bool       `url:"testmode,omitempty"`
+}
+```
+
+RefundOptions describes refund endpoint valid query string parameters. See: https://docs.mollie.com/reference/v2/refunds-api/get-refund.
+
+#### type EmbedValue
+
+```go
+type EmbedValue string
+```
+
+EmbedValue describes the valid value of embed query string.
+
+```go
+const (
+	EmbedPayment EmbedValue = "payment"
+)
+```
+
+Valid Embed query string value.
+
+#### type ListRefundOptions
+
+```go
+type ListRefundOptions struct {
+	From      string     `url:"from,omitempty"`
+	Limit     string     `url:"limit,omitempty"`
+	ProfileID string     `url:"profileId,omitempty"`
+	TestMode  bool       `url:"testmode,omitempty"`
+	Embed     EmbedValue `url:"embed,omitempty"`
+}
+```
+
+ListRefundOptions describes list refund endpoint valid query string parameters. See: https://docs.mollie.com/reference/v2/refunds-api/list-refunds.
+
+#### type RefundsService
+
+```go
+type RefundsService service
+```
+
+RefundsService instance operates over refund resources.
+
+#### func (\*RefundsService) Get
+
+```go
+func (rs *RefundsService) Get(paymentID, refundID string, options *RefundOptions) (refund Refund, err error)
+```
+
+Retrieve a single refund by its ID. If you do not know the original payment’s ID, you can use the List payment refunds endpoint.
+
+#### func (\*RefundsService) Create
+
+```go
+func (rs *RefundsService) Create(paymentID string, re Refund, options *RefundOptions) (rf Refund, err error)
+```
+
+Create a refund payment request. See https://docs.mollie.com/reference/v2/refunds-api/create-refund.
+
+#### func (\*RefundsService) Cancel
+
+```go
+func (rs *RefundsService) Cancel(paymentID, refundID string, options *RefundOptions) (err error)
+```
+
+Cancel try to cancel the refund request. The refund can only be canceled while the refund’s status is either queued or pending. See https://docs.mollie.com/reference/v2/refunds-api/cancel-refund
+
+#### func (\*RefundService) ListRefund
+
+```go
+func (rs *RefundsService) ListRefund(options *ListRefundOptions) (rl RefundList, err error)
+```
+
+ListRefund calls the top level https://api.mollie.com/v2/refunds. See https://docs.mollie.com/reference/v2/refunds-api/list-refunds.
+
+#### func (\*RefundService) ListRefundPayment
+
+```go
+func (rs *RefundsService) ListRefundPayment(paymentID string, options *ListRefundOptions) (rl RefundList, err error)
+```
+
+ListRefundPayment calls the payment-specific https://api.mollie.com/v2/payments/*paymentId*/refunds. Only refunds for that specific payment are returned. See https://docs.mollie.com/reference/v2/refunds-api/list-refunds
\ No newline at end of file
diff --git a/mollie/common_types.go b/mollie/common_types.go
index 8b44dd1..67de268 100644
--- a/mollie/common_types.go
+++ b/mollie/common_types.go
@@ -97,3 +97,39 @@ type PaginationLinks struct {
 	Next     *URL `json:"next,omitempty"`
 	Docs     *URL `json:"documentation,omitempty"`
 }
+
+// CategoryCode specifies an industry or category
+type CategoryCode uint
+
+// Available category codes
+const (
+	BookMagazinesAndNewspapers      CategoryCode = 5192
+	GeneralMerchandise              CategoryCode = 5399
+	FoodAndDrinks                   CategoryCode = 5499
+	AutomotiveProducts              CategoryCode = 5533
+	ChildrenProducts                CategoryCode = 5641
+	ClothingAndShoes                CategoryCode = 5651
+	ElectronicsComputersAndSoftware CategoryCode = 5732
+	HostingOrVpnServices            CategoryCode = 5734
+	Entertainment                   CategoryCode = 5735
+	CreditsOrVouchersOrGiftCards    CategoryCode = 5815
+	Alcohol                         CategoryCode = 5921
+	JewelryAndAccessories           CategoryCode = 5944
+	HealthAndBeautyProducts         CategoryCode = 5977
+	FinancialServices               CategoryCode = 6012
+	Consultancy                     CategoryCode = 7299
+	TravelRentalAndTransportation   CategoryCode = 7999
+	AdvisingOrCoachingOrTraining    CategoryCode = 8299
+	CharityAndDonations             CategoryCode = 8398
+	PoliticalParties                CategoryCode = 8699
+	Others                          CategoryCode = 0
+)
+
+// Mode contains information about the creation environment.
+type Mode string
+
+// Valid modes
+const (
+	LiveMode Mode = "live"
+	TestMode Mode = "test"
+)
diff --git a/mollie/mollie.go b/mollie/mollie.go
index 20ddc31..b9433c3 100644
--- a/mollie/mollie.go
+++ b/mollie/mollie.go
@@ -41,6 +41,8 @@ type Client struct {
 	Methods       *MethodsService
 	Invoices      *InvoicesService
 	Organizations *OrganizationsService
+	Profiles      *ProfilesService
+	Refunds       *RefundsService
 }
 
 type service struct {
@@ -154,6 +156,8 @@ func NewClient(baseClient *http.Client, c *Config) (mollie *Client, err error) {
 	mollie.Methods = (*MethodsService)(&mollie.common)
 	mollie.Invoices = (*InvoicesService)(&mollie.common)
 	mollie.Organizations = (*OrganizationsService)(&mollie.common)
+	mollie.Profiles = (*ProfilesService)(&mollie.common)
+	mollie.Refunds = (*RefundsService)(&mollie.common)
 
 	// Parse authorization from environment
 	if tkn, ok := os.LookupEnv(APITokenEnv); ok {
diff --git a/mollie/orders.go b/mollie/orders.go
new file mode 100644
index 0000000..ffda7c8
--- /dev/null
+++ b/mollie/orders.go
@@ -0,0 +1,141 @@
+package mollie
+
+import (
+	"time"
+)
+
+// Orders explain the items that customers need to pay for
+type Orders struct {
+	Resource            string         `json:"resource,omitempty"`
+	ID                  string         `json:"id,omitempty"`
+	ProfileID           string         `json:"profileId,omitempty"`
+	Method              *PaymentMethod `json:"method,omitempty"`
+	Mode                *Mode          `json:"mode,omitempty"`
+	Amount              *Amount        `json:"amount,omitempty"`
+	AmountCaptured      *Amount        `json:"amountCaptured,omitempty"`
+	AmountRefunded      *Amount        `json:"amountRefunded,omitempty"`
+	Status              *OrderStatus   `json:"status,omitempty"`
+	IsCancelable        bool           `json:"isCancelable,omitempty"`
+	BillingAddress      *OrderAddress  `json:"billingAddress,omitempty"`
+	ConsumerDateOfBirth *time.Time     `json:"consumerDateOfBirth,omitempty"`
+	OrderNumber         string         `json:"orderNumber,omitempty"`
+	ShippingAddress     *OrderAddress  `json:"shippingAddress,omitempty"`
+	Locale              *Locale        `json:"locale,omitempty"`
+	Metadata            interface{}    `json:"metadata,omitempty"`
+	RedirectURL         string         `json:"redirectUrl,omitempty"`
+	Lines               *OrderLines    `json:"lines,omitempty"`
+	WebhookURL          string         `json:"webhookUrl,omitempty"`
+	CreatedAt           *time.Time     `json:"createdAt,omitempty"`
+	ExpiresAt           *time.Time     `json:"expiresAt,omitempty"`
+	ExpiredAt           *time.Time     `json:"expiredAt,omitempty"`
+	PaidAt              *time.Time     `json:"paidAt,omitempty"`
+	AuthorizedAt        *time.Time     `json:"authorizedAt,omitempty"`
+	CanceledAt          *time.Time     `json:"canceledAt,omitempty"`
+	CompletedAt         *time.Time     `json:"completedAt,omitempty"`
+	Embedded            struct {
+		Payments []Payment `json:"payments,omitempty"`
+		Refunds  []Refund  `json:"refunds,omitempty"`
+	} `json:"_embedded,omitempty"`
+	Links *OrderLinks `json:"_links,omitempty"`
+}
+
+// OrderStatus describes the valid order status
+type OrderStatus string
+
+// Valid order status
+const (
+	Created    OrderStatus = "created"
+	Paid       OrderStatus = "paid"
+	Authorized OrderStatus = "authorized"
+	Canceled   OrderStatus = "canceled"
+	Shipping   OrderStatus = "shipping"
+	Completed  OrderStatus = "completed"
+	Expired    OrderStatus = "expired"
+)
+
+// OrderAddress identify both the address and the person the order is billed or shipped to.
+type OrderAddress struct {
+	OrganizationName string       `json:"organizationName,omitempty"`
+	Title            string       `json:"title,omitempty"`
+	GivenName        string       `json:"givenName,omitempty"`
+	FamilyName       string       `json:"familyName,omitempty"`
+	Email            string       `json:"email,omitempty"`
+	Phone            *PhoneNumber `json:"phone,omitempty"`
+	StreetAndNumber  string       `json:"stringAndNumber,omitempty"`
+	StreetAdditional string       `json:"streetAdditional,omitempty"`
+	PostalCode       string       `json:"postalCode,omitempty"`
+	City             string       `json:"city,omitempty"`
+	Region           string       `json:"region,omitempty"`
+	Country          string       `json:"country,omitempty"`
+}
+
+// OrderLinks describes an object with several URL objects
+// relevant to the order.
+// Every URL object will contain an href and a type field.
+type OrderLinks struct {
+	Self          *URL `json:"self,omitempty"`
+	Checkout      *URL `json:"checkout,omitempty"`
+	Documentation *URL `json:"documentation,omitempty"`
+}
+
+// OrderLines contain the actual things the customer bought.
+type OrderLines struct {
+	Resource           string           `json:"resource,omitempty"`
+	ID                 string           `json:"id,omitempty"`
+	OrderID            string           `json:"orderId,omitempty"`
+	ProductType        *ProductType     `json:"type,omitempty"`
+	Name               string           `json:"name,omitempty"`
+	Status             *OrderLineStatus `json:"status,omitempty"`
+	IsCancelable       bool             `json:"isCancelable,omitempty"`
+	Quantity           int              `json:"quantity,omitempty"`
+	QuantityShipped    int              `json:"quantityShipped,omitempty"`
+	AmountShipped      *Amount          `json:"amountShipped,omitempty"`
+	QuantityRefunded   int              `json:"quantityRefunded,omitempty"`
+	AmountRefunded     *Amount          `json:"amountRefunded,omitempty"`
+	QuantityCanceled   int              `json:"quantityCanceled,omitempty"`
+	AmountCanceled     *Amount          `json:"amountCanceled,omitempty"`
+	ShippableQuantity  int              `json:"shippableQuantity,omitempty"`
+	RefundableQuantity int              `json:"refundableQuantity,omitempty"`
+	CancelableQuantity int              `json:"cancelableQuantity,omitempty"`
+	UnitPrice          *Amount          `json:"unitPrice,omitempty"`
+	DiscountAmount     *Amount          `json:"discountAmount,omitempty"`
+	TotalAmount        *Amount          `json:"totalAmount,omitempty"`
+	VatRate            string           `json:"vatRate,omitempty"`
+	VatAmount          *Amount          `json:"vatAmount,omitempty"`
+	SKU                string           `json:"sku,omitempty"`
+	CreatedAt          *time.Time       `json:"createdAt,omitempty"`
+	Links              *OrderLineLinks  `json:"_links,omitempty"`
+}
+
+// ProductType describes the type of product bought, for example, a physical or a digital product.
+type ProductType string
+
+// Valid product type
+const (
+	Physical        ProductType = "physical"
+	Discount        ProductType = "discount"
+	Digital         ProductType = "digital"
+	ShippingFee     ProductType = "shipping_fee"
+	StoreCredit     ProductType = "store_credit"
+	GiftCardProduct ProductType = "gift_card"
+	Surcharge       ProductType = "surcharge"
+)
+
+// OrderLineStatus describes status of the order line
+type OrderLineStatus string
+
+// Valid order line status
+const (
+	OrderLineCreated    OrderLineStatus = "created"
+	OrderLineAuthorized OrderLineStatus = "authorized"
+	OrderLinePaid       OrderLineStatus = "paid"
+	OrderLineShipping   OrderLineStatus = "shipping"
+	OrderLineCanceled   OrderLineStatus = "canceled"
+	OrderLineCompleted  OrderLineStatus = "completed"
+)
+
+// OrderLineLinks describes object with several URL objects relevant to the order line
+type OrderLineLinks struct {
+	ProductURL *URL `json:"productUrl,omitempty"`
+	ImageURL   *URL `json:"imageUrl,omitempty"`
+}
diff --git a/mollie/payments.go b/mollie/payments.go
index 203dd18..87a523c 100644
--- a/mollie/payments.go
+++ b/mollie/payments.go
@@ -9,15 +9,6 @@ import (
 	"github.com/google/go-querystring/query"
 )
 
-// Mode contains information about the payment creation environment.
-type Mode string
-
-// Valid modes
-const (
-	LiveMode Mode = "live"
-	TestMode Mode = "test"
-)
-
 // PaymentMethod is a payment method supported by Mollie.
 type PaymentMethod string
 
diff --git a/mollie/profiles.go b/mollie/profiles.go
new file mode 100644
index 0000000..06af748
--- /dev/null
+++ b/mollie/profiles.go
@@ -0,0 +1,199 @@
+package mollie
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/google/go-querystring/query"
+)
+
+// ProfileStatus determines whether the profile is able to receive live payments
+type ProfileStatus string
+
+// Possible profile statuses
+const (
+	StatusUnverified ProfileStatus = "unverified"
+	StatusVerified   ProfileStatus = "verified"
+	StatusBlocked    ProfileStatus = "blocked"
+)
+
+// Profile will usually reflect the trademark or brand name
+// of the profile’s website or application.
+type Profile struct {
+	ID           string       `json:"id,omitempty"`
+	CategoryCode CategoryCode `json:"categoryCode,omitempty"`
+	CreatedAt    *time.Time   `json:"createdAt,omitempty"`
+	Email        string       `json:"email,omitempty"`
+	Mode         Mode         `json:"mode,omitempty"`
+	Name         string       `json:"name,omitempty"`
+	Phone        PhoneNumber  `json:"phone,omitempty"`
+	Resource     string       `json:"resource,omitempty"`
+	Review       struct {
+		Status string `json:"status,omitempty"`
+	} `json:"review,omitempty"`
+	Status  ProfileStatus `json:"status,omitempty"`
+	Website string        `json:"website,omitempty"`
+	Links   ProfileLinks  `json:"_links,omitempty"`
+}
+
+// ProfileLinks contains URL's to relevant information related to
+// a profile.
+type ProfileLinks struct {
+	Self               URL `json:"self,omitempty"`
+	Chargebacks        URL `json:"chargebacks,omitempty"`
+	Methods            URL `json:"methods,omitempty"`
+	Refunds            URL `json:"refunds,omitempty"`
+	CheckoutPreviewURL URL `json:"checkoutPreviewUrl,omitempty"`
+	Documentation      URL `json:"documentation,omitempty"`
+}
+
+// ProfileListOptions are optional query string parameters for the list profiles request
+type ProfileListOptions struct {
+	From  string `url:"from,omitempty"`
+	Limit uint   `url:"limit,omitempty"`
+}
+
+// ProfileList contains a list of profiles for your account.
+type ProfileList struct {
+	Count    int             `json:"count,omitempty"`
+	Embedded profiles        `json:"_embedded,omitempty"`
+	Links    PaginationLinks `json:"_links,omitempty"`
+}
+
+type profiles struct {
+	Profiles []Profile `json:"profiles,omitempty"`
+}
+
+// ProfilesService operates over profile resource
+type ProfilesService service
+
+// List returns all the profiles for the authenticated account
+func (ps *ProfilesService) List(options *ProfileListOptions) (pl *ProfileList, err error) {
+	u := "v2/profiles"
+	if options != nil {
+		v, _ := query.Values(options)
+		u = fmt.Sprintf("%s?%s", u, v.Encode())
+	}
+	req, err := ps.client.NewAPIRequest(http.MethodGet, u, nil)
+	if err != nil {
+		return
+	}
+	res, err := ps.client.Do(req)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(res.content, &pl); err != nil {
+		return
+	}
+	return
+}
+
+// Get retrieves the a profile by ID.
+func (ps *ProfilesService) Get(id string) (p *Profile, err error) {
+	return ps.get(id)
+}
+
+// Current returns the profile belonging to the API key.
+// This method only works when using API keys.
+func (ps *ProfilesService) Current() (p *Profile, err error) {
+	return ps.get("me")
+}
+
+func (ps *ProfilesService) get(id string) (p *Profile, err error) {
+	u := fmt.Sprintf("v2/profiles/%s", id)
+	req, err := ps.client.NewAPIRequest(http.MethodGet, u, nil)
+	if err != nil {
+		return
+	}
+	res, err := ps.client.Do(req)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(res.content, &p); err != nil {
+		return
+	}
+	return
+}
+
+// Create stores a new profile in your Mollie account.
+func (ps *ProfilesService) Create(np *Profile) (p *Profile, err error) {
+	req, err := ps.client.NewAPIRequest(http.MethodPost, "v2/profiles", np)
+	if err != nil {
+		return
+	}
+	res, err := ps.client.Do(req)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(res.content, &p); err != nil {
+		return
+	}
+	return
+}
+
+// Update allows you to perform mutations on a profile
+func (ps *ProfilesService) Update(id string, up *Profile) (p *Profile, err error) {
+	u := fmt.Sprintf("v2/profiles/%s", id)
+	req, err := ps.client.NewAPIRequest(http.MethodPatch, u, up)
+	if err != nil {
+		return
+	}
+	res, err := ps.client.Do(req)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(res.content, &p); err != nil {
+		return
+	}
+	return
+}
+
+// Delete  enables profile deletions, rendering the profile unavailable
+// for further API calls and transactions.
+func (ps *ProfilesService) Delete(id string) (err error) {
+	u := fmt.Sprintf("v2/profiles/%s", id)
+	req, err := ps.client.NewAPIRequest(http.MethodDelete, u, nil)
+	if err != nil {
+		return
+	}
+	_, err = ps.client.Do(req)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// EnablePaymentMethod enables a payment method on a specific or authenticated profile.
+// If you're using API tokens for authentication, pass "me" as id.
+func (ps *ProfilesService) EnablePaymentMethod(id string, pm PaymentMethod) (pmi *PaymentMethodInfo, err error) {
+	u := fmt.Sprintf("v2/profiles/%s/methods/%s", id, pm)
+	req, err := ps.client.NewAPIRequest(http.MethodPost, u, nil)
+	if err != nil {
+		return
+	}
+	res, err := ps.client.Do(req)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(res.content, &pmi); err != nil {
+		return
+	}
+	return
+}
+
+// DisablePaymentMethod disables a payment method on a specific or authenticated profile.
+// If you're using API tokens for authentication, pass "me" as id.
+func (ps *ProfilesService) DisablePaymentMethod(id string, pm PaymentMethod) (err error) {
+	u := fmt.Sprintf("v2/profiles/%s/methods/%s", id, pm)
+	req, err := ps.client.NewAPIRequest(http.MethodDelete, u, nil)
+	if err != nil {
+		return
+	}
+	_, err = ps.client.Do(req)
+	if err != nil {
+		return
+	}
+	return
+}
diff --git a/mollie/refunds.go b/mollie/refunds.go
new file mode 100644
index 0000000..39ff8d9
--- /dev/null
+++ b/mollie/refunds.go
@@ -0,0 +1,242 @@
+package mollie
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/google/go-querystring/query"
+)
+
+// Refund describe a refund for a certain payment.
+type Refund struct {
+	Resource         string        `json:"resource,omitempty"`
+	ID               string        `json:"id,omitempty"`
+	Amount           *Amount       `json:"amount,omitempty"`
+	SettlementID     string        `json:"settlementId,omitempty"`
+	SettlementAmount *Amount       `json:"settlementAmount,omitempty"`
+	Description      string        `json:"description,omitempty"`
+	Metadata         interface{}   `json:"metadata,omitempty"`
+	Status           *RefundStatus `json:"status,omitempty"`
+	Lines            *OrderLines   `json:"lines,omitempty"`
+	PaymentID        string        `json:"paymentId,omitempty"`
+	OrderID          string        `json:"orderId,omitempty"`
+	CreatedAt        *time.Time    `json:"createdAt,omitempty"`
+	Links            *RefundLinks  `json:"_links,omitempty"`
+}
+
+// RefundList describes how a list of refunds will be retrieved by Mollie.
+type RefundList struct {
+	Count    int `json:"count,omitempty"`
+	Embedded struct {
+		Refunds []Refund
+	} `json:"_embedded,omitempty"`
+	Links PaginationLinks `json:"_links,omitempty"`
+}
+
+// RefundStatus describes the status of the refund.
+type RefundStatus string
+
+// Valid refund status.
+const (
+	Queued     RefundStatus = "queued"
+	Pending    RefundStatus = "pending"
+	Processing RefundStatus = "processing"
+	Refunded   RefundStatus = "refunded"
+	Failed     RefundStatus = "failed"
+)
+
+// RefundLinks describes all the possible links to be returned with
+// a Refund object.
+type RefundLinks struct {
+	Self       *URL `json:"self,omitempty"`
+	Payment    *URL `json:"payment,omitempty"`
+	Settlement *URL `json:"settlement,omitempty"`
+	Order      *URL `json:"order,omitempty"`
+}
+
+// RefundOptions describes refund endpoint valid query string parameters.
+//
+// See: https://docs.mollie.com/reference/v2/refunds-api/get-refund.
+type RefundOptions struct {
+	Embed EmbedValue `url:"embed,omitempty"`
+}
+
+// EmbedValue describes the valid value of embed query string.
+type EmbedValue string
+
+// Valid Embed query string value.
+const (
+	EmbedPayment EmbedValue = "payment"
+)
+
+// ListRefundOptions describes list refund endpoint valid query string parameters.
+//
+// See: https://docs.mollie.com/reference/v2/refunds-api/list-refunds.
+type ListRefundOptions struct {
+	From      string     `url:"from,omitempty"`
+	Limit     string     `url:"limit,omitempty"`
+	ProfileID string     `url:"profileId,omitempty"`
+	Embed     EmbedValue `url:"embed,omitempty"`
+}
+
+// RefundsService instance operates over refund resources.
+type RefundsService service
+
+// Get retrieve a single refund by its ID.
+//
+// If you do not know the original payment’s ID, you can use the List payment refunds endpoint.
+func (rs *RefundsService) Get(paymentID, refundID string, options *RefundOptions) (refund Refund, err error) {
+	u := fmt.Sprintf("v2/payments/%s/refunds/%s", paymentID, refundID)
+	if options != nil {
+		v, _ := query.Values(options)
+		u = fmt.Sprintf("%s?%s", u, v.Encode())
+	}
+
+	req, err := rs.client.NewAPIRequest(http.MethodGet, u, options)
+	if err != nil {
+		return
+	}
+
+	res, err := rs.client.Do(req)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &refund); err != nil {
+		return
+	}
+
+	return
+}
+
+var (
+	requiredCreateParamRefund = "parameter required for creating a refund: %+v"
+)
+
+// Create a refund payment request.
+//
+// See https://docs.mollie.com/reference/v2/refunds-api/create-refund.
+func (rs *RefundsService) Create(paymentID string, re Refund, options *RefundOptions) (rf Refund, err error) {
+	err = re.checkMandatoryParam()
+	if err != nil {
+		return re, err
+	}
+
+	u := fmt.Sprintf("v2/payments/%s/refunds", paymentID)
+	if options != nil {
+		v, _ := query.Values(options)
+		u = fmt.Sprintf("%s?%s", u, v.Encode())
+	}
+
+	req, err := rs.client.NewAPIRequest(http.MethodPost, u, re)
+	if err != nil {
+		return
+	}
+
+	res, err := rs.client.Do(req)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &rf); err != nil {
+		return
+	}
+
+	return
+}
+
+// Cancel try to cancel the refund request.
+// The refund can only be canceled while the refund’s status is either queued or pending.
+// See https://docs.mollie.com/reference/v2/refunds-api/cancel-refund
+func (rs *RefundsService) Cancel(paymentID, refundID string, options *RefundOptions) (err error) {
+	u := fmt.Sprintf("v2/payments/%s/refunds/%s", paymentID, refundID)
+	if options != nil {
+		v, _ := query.Values(options)
+		u = fmt.Sprintf("%s?%s", u, v.Encode())
+	}
+
+	req, err := rs.client.NewAPIRequest(http.MethodDelete, u, nil)
+	if err != nil {
+		return
+	}
+
+	_, err = rs.client.Do(req)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// ListRefund calls the top level https://api.mollie.com/v2/refunds.
+//
+// See https://docs.mollie.com/reference/v2/refunds-api/list-refunds.
+func (rs *RefundsService) ListRefund(options *ListRefundOptions) (rl RefundList, err error) {
+	u := fmt.Sprintf("v2/refunds")
+	if options != nil {
+		v, _ := query.Values(options)
+		u = fmt.Sprintf("%s?%s", u, v.Encode())
+	}
+
+	req, err := rs.client.NewAPIRequest(http.MethodGet, u, nil)
+	if err != nil {
+		return
+	}
+
+	res, err := rs.client.Do(req)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &rl); err != nil {
+		return
+	}
+
+	return
+}
+
+// ListRefundPayment calls the payment-specific
+// https://api.mollie.com/v2/payments/*paymentId*/refunds.
+// Only refunds for that specific payment are returned.
+// See https://docs.mollie.com/reference/v2/refunds-api/list-refunds
+func (rs *RefundsService) ListRefundPayment(paymentID string, options *ListRefundOptions) (rl RefundList, err error) {
+	u := fmt.Sprintf("v2/payments/%s/refunds", paymentID)
+	if options != nil {
+		v, _ := query.Values(options)
+		u = fmt.Sprintf("%s?%s", u, v.Encode())
+	}
+
+	req, err := rs.client.NewAPIRequest(http.MethodGet, u, nil)
+	if err != nil {
+		return
+	}
+
+	res, err := rs.client.Do(req)
+	if err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(res.content, &rl); err != nil {
+		return
+	}
+
+	return
+}
+
+func (re *Refund) checkMandatoryParam() (err error) {
+	if re.Amount == nil {
+		return fmt.Errorf(requiredCreateParamRefund, re.Amount)
+	}
+
+	if re.Amount.Currency == "" {
+		return fmt.Errorf(requiredCreateParamRefund, re.Amount.Currency)
+	}
+
+	if re.Amount.Value == "" {
+		return fmt.Errorf(requiredCreateParamRefund, re.Amount.Value)
+	}
+
+	return nil
+}


Test patch:
diff --git a/mollie/profiles_test.go b/mollie/profiles_test.go
new file mode 100644
index 0000000..135dc72
--- /dev/null
+++ b/mollie/profiles_test.go
@@ -0,0 +1,320 @@
+package mollie
+
+import (
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	"github.com/VictorAvelar/mollie-api-go/testdata"
+)
+
+func TestProfilesService_Get(t *testing.T) {
+	setup()
+	defer teardown()
+	id := "pfl_v9hTwCvYqw"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/profiles/"+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.GetProfileResponse))
+	})
+
+	p, err := tClient.Profiles.Get(id)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if p.ID != id {
+		t.Errorf("unexpected response, want: %v got: %v", p.ID, id)
+	}
+}
+
+func TestProfilesService_Current(t *testing.T) {
+	setup()
+	defer teardown()
+	id := "pfl_v9hTwCvYqw"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/profiles/me", 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.GetProfileResponse))
+	})
+
+	p, err := tClient.Profiles.Current()
+	if err != nil {
+		t.Error(err)
+	}
+
+	if p.ID != id {
+		t.Errorf("unexpected response, want: %v got: %v", p.ID, id)
+	}
+}
+
+func TestProfilesService_List(t *testing.T) {
+	setup()
+	defer teardown()
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/profiles", 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.GetProfilesListResponse))
+	})
+
+	opt := &ProfileListOptions{
+		Limit: 10,
+	}
+
+	pl, err := tClient.Profiles.List(opt)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if len(pl.Embedded.Profiles) < 1 {
+		t.Errorf("returned empty list")
+	}
+}
+
+func TestProfilesService_Create(t *testing.T) {
+	setup()
+	defer teardown()
+	id := "pfl_v9hTwCvYqw"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/profiles", func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, "POST")
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusCreated)
+		_, _ = w.Write([]byte(testdata.CreateProfileResponse))
+	})
+	np := &Profile{
+		ID:           "pfl_v9hTwCvYqw",
+		CategoryCode: GeneralMerchandise,
+		Email:        "info@mywebsite.com",
+		Mode:         LiveMode,
+		Name:         "My website name",
+	}
+
+	p, err := tClient.Profiles.Create(np)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if p.ID != np.ID {
+		t.Errorf("unexpected response, want: %v got: %v", p.ID, id)
+	}
+}
+
+func TestProfilesService_Update(t *testing.T) {
+	setup()
+	defer teardown()
+	id := "pfl_v9hTwCvYqw"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/profiles/"+id, func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, "PATCH")
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(testdata.GetProfileResponse))
+	})
+
+	np := &Profile{
+		ID:           "pfl_v9hTwCvYqw",
+		CategoryCode: GeneralMerchandise,
+		Email:        "info@mywebsite.com",
+		Mode:         LiveMode,
+		Name:         "My website name",
+		Phone:        "",
+	}
+
+	p, err := tClient.Profiles.Update(id, np)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if p.ID != id {
+		t.Errorf("unexpected response, want: %v got: %v", p.ID, id)
+	}
+}
+
+func TestProfilesService_Delete(t *testing.T) {
+	setup()
+	defer teardown()
+	id := "pfl_v9hTwCvYqw"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/profiles/"+id, func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, "DELETE")
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	err := tClient.Profiles.Delete(id)
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+func TestProfilesService_EnablePaymentMethod(t *testing.T) {
+	setup()
+	defer teardown()
+	id := "pfl_v9hTwCvYqw"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/profiles/"+id+"/methods/bancontact", func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, "POST")
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(testdata.EnablePaymentMethodResponse))
+	})
+
+	res, err := tClient.Profiles.EnablePaymentMethod(id, Bancontact)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if res.ID != string(Bancontact) {
+		t.Errorf("unexpected response, want: %v got: %v", Bancontact, res.ID)
+	}
+}
+
+func TestProfilesService_DisablePaymentMethod(t *testing.T) {
+	setup()
+	defer teardown()
+	id := "pfl_v9hTwCvYqw"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+
+	tMux.HandleFunc("/v2/profiles/"+id+"/methods/bancontact", func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, "DELETE")
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	err := tClient.Profiles.DisablePaymentMethod(id, Bancontact)
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+func TestProfilesService_HttpRequestErrors(t *testing.T) {
+	setup()
+	defer teardown()
+	tMux.HandleFunc("/v2/profiles/", errorHandler)
+
+	p := Profile{}
+
+	_, cerr := tClient.Profiles.Create(&p)
+	_, rerr := tClient.Profiles.List(nil)
+	_, uerr := tClient.Profiles.Update("1212", &p)
+	derr := tClient.Profiles.Delete("1212")
+	_, gerr := tClient.Profiles.Get("1212")
+	_, ccerr := tClient.Profiles.Current()
+	dderr := tClient.Profiles.DisablePaymentMethod("1212", PayPal)
+	_, eperr := tClient.Profiles.EnablePaymentMethod("1212", PayPal)
+
+	tests := []error{cerr, rerr, uerr, derr, gerr, ccerr, eperr, dderr}
+
+	for _, tt := range tests {
+		if tt == nil {
+			t.Fail()
+		}
+	}
+}
+
+func TestProfilesService_EncodingResponseErrors(t *testing.T) {
+	setup()
+	defer teardown()
+	tMux.HandleFunc("/v2/profiles/", encodingHandler)
+
+	p := Profile{}
+
+	_, cerr := tClient.Profiles.Create(&p)
+	_, rerr := tClient.Profiles.List(nil)
+	_, uerr := tClient.Profiles.Update("1212", &p)
+	_, gerr := tClient.Profiles.Get("1212")
+	_, ccerr := tClient.Profiles.Current()
+	_, eperr := tClient.Profiles.EnablePaymentMethod("1212", PayPal)
+
+	tests := []error{cerr, rerr, uerr, gerr, ccerr, eperr}
+
+	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 TestProfilesService_NewAPIRequestBaseError(t *testing.T) {
+	setup()
+	defer teardown()
+	u, _ := url.Parse(tServer.URL)
+	tClient.BaseURL = u
+	tMux.HandleFunc("/v2/profiles/", errorHandler)
+
+	p := Profile{}
+
+	_, cerr := tClient.Profiles.Create(&p)
+	_, rerr := tClient.Profiles.List(nil)
+	_, uerr := tClient.Profiles.Update("1212", &p)
+	_, gerr := tClient.Profiles.Get("1212")
+	_, ccerr := tClient.Profiles.Current()
+	ddcerr := tClient.Profiles.Delete("1212")
+	_, eperr := tClient.Profiles.EnablePaymentMethod("1212", PayPal)
+	deperr := tClient.Profiles.DisablePaymentMethod("1212", PayPal)
+
+	tests := []error{cerr, rerr, uerr, gerr, ccerr, eperr, deperr, ddcerr}
+
+	for _, tt := range tests {
+		if tt != errBadBaseURL {
+			t.Error(tt)
+		}
+	}
+}
diff --git a/mollie/refunds_test.go b/mollie/refunds_test.go
new file mode 100644
index 0000000..b5b0b10
--- /dev/null
+++ b/mollie/refunds_test.go
@@ -0,0 +1,308 @@
+package mollie
+
+import (
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	"github.com/VictorAvelar/mollie-api-go/testdata"
+)
+
+func TestRefundsService_Get(t *testing.T) {
+	setup()
+	defer teardown()
+
+	paymentID := "tr_WDqYK6vllg"
+	refundID := "re_4qqhO89gsT"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+	tMux.HandleFunc("/v2/payments/"+paymentID+"/refunds/"+refundID, func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, http.MethodGet)
+
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(testdata.GetRefundResponse))
+	})
+
+	opt := &RefundOptions{
+		Embed: EmbedPayment,
+	}
+
+	res, err := tClient.Refunds.Get(paymentID, refundID, opt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if res.ID != refundID {
+		t.Errorf("mismatching info. want %v got %v", refundID, res.ID)
+	}
+}
+
+func TestRefundsService_Create(t *testing.T) {
+	setup()
+	defer teardown()
+
+	paymentID := "tr_WDqYK6vllg"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+	tMux.HandleFunc("/v2/payments/"+paymentID+"/refunds", func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, http.MethodPost)
+
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(testdata.GetRefundResponse))
+	})
+
+	refund := &Refund{
+		Amount: &Amount{
+			Currency: "EUR",
+			Value:    "20",
+		},
+		Description: "Order #33",
+	}
+
+	res, err := tClient.Refunds.Create(paymentID, *refund, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if res.Description != refund.Description {
+		t.Errorf("mismatching info. want %s got %s", refund.Description, res.Description)
+	}
+}
+
+func TestRefundsService_CreateInvalidParams(t *testing.T) {
+	setup()
+	defer teardown()
+
+	paymentID := "tr_WDqYK6vllg"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+	tMux.HandleFunc("/v2/payments/"+paymentID+"/refunds", func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, http.MethodPost)
+
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(testdata.GetRefundResponse))
+	})
+
+	refcurr := Refund{
+		Amount: &Amount{
+			Currency: "IDR",
+		},
+	}
+
+	refval := Refund{
+		Amount: &Amount{
+			Value: "100000",
+		},
+	}
+
+	_, errcurr := tClient.Refunds.Create(paymentID, refcurr, nil)
+	_, errval := tClient.Refunds.Create(paymentID, refval, nil)
+
+	tests := []error{errcurr, errval}
+
+	for _, test := range tests {
+		if test == nil {
+			t.Fail()
+		} else if !strings.Contains(test.Error(), "parameter required") {
+			t.Errorf("unexpected error %v", test)
+		}
+	}
+}
+
+func TestRefundsService_Cancel(t *testing.T) {
+	setup()
+	defer teardown()
+
+	paymentID := "tr_7UhSN1zuXS"
+	refundID := "re_4qqhO89gsT"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+	tMux.HandleFunc("/v2/payments/"+paymentID+"/refunds/"+refundID, func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, http.MethodDelete)
+
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	err := tClient.Refunds.Cancel(paymentID, refundID, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestRefundsService_List(t *testing.T) {
+	setup()
+	defer teardown()
+
+	_ = tClient.WithAuthenticationValue("test_token")
+	tMux.HandleFunc("/v2/refunds", func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, http.MethodGet)
+
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		_, _ = w.Write([]byte(testdata.GetRefundListResponse))
+	})
+
+	opt := &ListRefundOptions{
+		ProfileID: "pfl_3RkSN1zuPE",
+	}
+
+	res, err := tClient.Refunds.ListRefund(opt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if res.Count == 0 {
+		t.Errorf("mismatching info. want >0 got %v", res.Count)
+	}
+}
+
+func TestServiceRefunds_ListPayment(t *testing.T) {
+	setup()
+	defer teardown()
+
+	paymentID := "tr_7UhSN1zuXS"
+
+	_ = tClient.WithAuthenticationValue("test_token")
+	tMux.HandleFunc("/v2/payments/"+paymentID+"/refunds", func(w http.ResponseWriter, r *http.Request) {
+		testHeader(t, r, AuthHeader, "Bearer test_token")
+		testMethod(t, r, http.MethodGet)
+
+		if _, ok := r.Header[AuthHeader]; !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+		}
+
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(testdata.GetRefundListResponse))
+	})
+
+	opt := &ListRefundOptions{
+		From:  "re_4qqhO89gsT",
+		Limit: "100",
+	}
+
+	res, err := tClient.Refunds.ListRefundPayment(paymentID, opt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if res.Count == 0 {
+		t.Errorf("mismatching info. want >0 got %v", res.Count)
+	}
+}
+
+func TestRefundsService_HttpRequestErrors(t *testing.T) {
+	setup()
+	defer teardown()
+	tMux.HandleFunc("/v2/refunds/", errorHandler)
+
+	refund := Refund{
+		Amount: &Amount{
+			Currency: "IDR",
+			Value:    "100000",
+		},
+		Description: "Order #12345",
+	}
+
+	_, cerr := tClient.Refunds.Create("123132", refund, nil)
+	_, rerr1 := tClient.Refunds.ListRefund(nil)
+	_, rerr2 := tClient.Refunds.ListRefundPayment("123132", nil)
+	derr := tClient.Refunds.Cancel("12123", "123123", nil)
+	_, rerr3 := tClient.Refunds.Get("1212", "12312", nil)
+
+	tests := []error{cerr, rerr1, rerr2, derr, rerr3}
+
+	for _, tt := range tests {
+		if tt == nil {
+			t.Fail()
+		}
+	}
+}
+
+func TestRefundsService_NewAPIRequestErrors(t *testing.T) {
+	setup()
+	defer teardown()
+	u, _ := url.Parse(tServer.URL)
+	tClient.BaseURL = u
+	tMux.HandleFunc("/v2/refunds/", errorHandler)
+
+	refund := Refund{
+		Amount: &Amount{
+			Currency: "IDR",
+			Value:    "100000",
+		},
+		Description: "Order #12345",
+	}
+
+	_, cerr := tClient.Refunds.Create("123132", refund, nil)
+	_, rerr1 := tClient.Refunds.ListRefund(nil)
+	_, rerr2 := tClient.Refunds.ListRefundPayment("123132", nil)
+	derr := tClient.Refunds.Cancel("12123", "123123", nil)
+	_, rerr3 := tClient.Refunds.Get("1212", "12312", nil)
+
+	tests := []error{cerr, rerr1, rerr2, derr, rerr3}
+
+	for _, tt := range tests {
+		if tt != errBadBaseURL {
+			t.Fail()
+		}
+	}
+}
+
+func TestRefundsService_EncodingResponseErrors(t *testing.T) {
+	setup()
+	defer teardown()
+
+	paymentID := "tr_7UhSN1zuXS"
+	refundID := "re_4qqhO89gsT"
+
+	tMux.HandleFunc("/v2/payments/"+paymentID+"/refunds", encodingHandler)
+	tMux.HandleFunc("/v2/refunds/", encodingHandler)
+	tMux.HandleFunc("/v2/payments/"+paymentID+"/refunds/"+refundID, encodingHandler)
+
+	refund := Refund{
+		Amount: &Amount{
+			Currency: "IDR",
+			Value:    "100000",
+		},
+		Description: "Order #12345",
+	}
+
+	_, cerr := tClient.Refunds.Create(paymentID, refund, nil)
+	_, rerr1 := tClient.Refunds.ListRefund(nil)
+	_, rerr2 := tClient.Refunds.ListRefundPayment(paymentID, nil)
+	_, rerr3 := tClient.Refunds.Get(paymentID, refundID, nil)
+
+	tests := []error{cerr, rerr1, rerr2, rerr3}
+
+	for _, tt := range tests {
+		if tt == nil {
+			t.Fail()
+		} else if !strings.Contains(tt.Error(), "invalid character") {
+			t.Errorf("unexpected error %v", tt)
+		}
+	}
+}
diff --git a/testdata/profiles.go b/testdata/profiles.go
new file mode 100644
index 0000000..a7c2a41
--- /dev/null
+++ b/testdata/profiles.go
@@ -0,0 +1,231 @@
+package testdata
+
+// GetProfileResponse example
+const GetProfileResponse = `{
+    "resource": "profile",
+    "id": "pfl_v9hTwCvYqw",
+    "mode": "live",
+    "name": "My website name",
+    "website": "https://www.mywebsite.com",
+    "email": "info@mywebsite.com",
+    "phone": "+31208202070",
+    "categoryCode": 5399,
+    "status": "verified",
+    "review": {
+        "status": "pending"
+    },
+    "createdAt": "2018-03-20T09:28:37+00:00",
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/profiles/pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "chargebacks": {
+            "href": "https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "methods": {
+            "href": "https://api.mollie.com/v2/methods?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "payments": {
+            "href": "https://api.mollie.com/v2/payments?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "refunds": {
+            "href": "https://api.mollie.com/v2/refunds?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "checkoutPreviewUrl": {
+            "href": "https://www.mollie.com/payscreen/preview/pfl_v9hTwCvYqw",
+            "type": "text/html"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/profiles-api/create-profile",
+            "type": "text/html"
+        }
+    }
+}`
+
+// GetProfilesListResponse example
+const GetProfilesListResponse = `{
+    "_embedded": {
+        "profiles": [
+            {
+                "resource": "profiles",
+                "id": "pfl_v9hTwCvYqw",
+                "mode": "live",
+                "name": "My website name",
+                "website": "https://www.mywebsite.com",
+                "email": "info@mywebsite.com",
+                "phone": "+31208202070",
+                "categoryCode": 5399,
+                "status": "verified",
+                "review": {
+                    "status": "pending"
+                },
+                "createdAt": "2018-03-20T09:28:37+00:00",
+                "_links": {
+                    "self": {
+                        "href": "https://api.mollie.com/v2/profiles/pfl_v9hTwCvYqw",
+                        "type": "application/hal+json"
+                    },
+                    "chargebacks": {
+                        "href": "https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw",
+                        "type": "application/hal+json"
+                    },
+                    "methods": {
+                        "href": "https://api.mollie.com/v2/methods?profileId=pfl_v9hTwCvYqw",
+                        "type": "application/hal+json"
+                    },
+                    "payments": {
+                        "href": "https://api.mollie.com/v2/payments?profileId=pfl_v9hTwCvYqw",
+                        "type": "application/hal+json"
+                    },
+                    "refunds": {
+                        "href": "https://api.mollie.com/v2/refunds?profileId=pfl_v9hTwCvYqw",
+                        "type": "application/hal+json"
+                    },
+                    "checkoutPreviewUrl": {
+                        "href": "https://www.mollie.com/payscreen/preview/pfl_v9hTwCvYqw",
+                        "type": "text/html"
+                    },
+                    "documentation": {
+                        "href": "https://docs.mollie.com/reference/v2/profiles-api/create-profile",
+                        "type": "text/html"
+                    }
+                }
+            }
+        ]
+    },
+    "count": 5,
+    "_links": {
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/profiles-api/list-profiles",
+            "type": "text/html"
+        },
+        "self": {
+            "href": "https://api.mollie.com/v2/profiles?limit=5",
+            "type": "application/hal+json"
+        },
+        "previous": null,
+        "next": {
+            "href": "https://api.mollie.com/v2/profiles?from=pfl_3RkSN1zuPE&limit=5",
+            "type": "application/hal+json"
+        }
+    }
+}`
+
+// CreateProfileResponse example
+const CreateProfileResponse = `
+{
+    "resource": "profile",
+    "id": "pfl_v9hTwCvYqw",
+    "mode": "live",
+    "name": "My website name",
+    "website": "https://www.mywebsite.com",
+    "email": "info@mywebsite.com",
+    "phone": "+31208202070",
+    "categoryCode": 5399,
+    "status": "unverified",
+    "createdAt": "2018-03-20T09:28:37+00:00",
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/profiles/pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "chargebacks": {
+            "href": "https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "methods": {
+            "href": "https://api.mollie.com/v2/methods?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "payments": {
+            "href": "https://api.mollie.com/v2/payments?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "refunds": {
+            "href": "https://api.mollie.com/v2/refunds?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "checkoutPreviewUrl": {
+            "href": "https://www.mollie.com/payscreen/preview/pfl_v9hTwCvYqw",
+            "type": "text/html"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/profiles-api/create-profile",
+            "type": "text/html"
+        }
+    }
+}`
+
+const UpdateProfileResponse = `{
+    "resource": "profile",
+    "id": "pfl_v9hTwCvYqw",
+    "mode": "live",
+    "name": "My website name - Update 1",
+    "website": "https://www.mywebsite2.com",
+    "email": "info@mywebsite2.com",
+    "phone": "+31208202070",
+    "categoryCode": 5399,
+    "status": "verified",
+    "review": {
+        "status": "pending"
+    },
+    "createdAt": "2018-03-20T09:28:37+00:00",
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/profiles/pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "chargebacks": {
+            "href": "https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "methods": {
+            "href": "https://api.mollie.com/v2/methods?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "payments": {
+            "href": "https://api.mollie.com/v2/payments?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "refunds": {
+            "href": "https://api.mollie.com/v2/refunds?profileId=pfl_v9hTwCvYqw",
+            "type": "application/hal+json"
+        },
+        "checkoutPreviewUrl": {
+            "href": "https://www.mollie.com/payscreen/preview/pfl_v9hTwCvYqw",
+            "type": "text/html"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/profiles-api/create-profile",
+            "type": "text/html"
+        }
+    }
+}`
+
+// EnablePaymentMethodResponse example
+const EnablePaymentMethodResponse = `{
+    "resource": "method",
+    "id": "bancontact",
+    "description": "Bancontact",
+    "image": {
+        "size1x": "https://www.mollie.com/external/icons/payment-methods/bancontact.png",
+        "size2x": "https://www.mollie.com/external/icons/payment-methods/bancontact%402x.png",
+        "svg": "https://www.mollie.com/external/icons/payment-methods/bancontact.svg"
+    },
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/methods/bancontact",
+            "type": "application/hal+json"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/profiles-api/activate-method",
+            "type": "text/html"
+        }
+    }
+}
+`
diff --git a/testdata/refunds.go b/testdata/refunds.go
new file mode 100644
index 0000000..c89ae20
--- /dev/null
+++ b/testdata/refunds.go
@@ -0,0 +1,87 @@
+package testdata
+
+// GetRefundResponse example
+const GetRefundResponse = `{
+    "resource": "refund",
+    "id": "re_4qqhO89gsT",
+    "amount": {
+        "currency": "EUR",
+        "value": "5.95"
+    },
+    "status": "pending",
+    "createdAt": "2018-03-14T17:09:02.0Z",
+    "description": "Order #33",
+    "metadata": {
+         "bookkeeping_id": 12345
+    },
+    "paymentId": "tr_WDqYK6vllg",
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/refunds/re_4qqhO89gsT",
+            "type": "application/hal+json"
+        },
+        "payment": {
+            "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg",
+            "type": "application/hal+json"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/refunds-api/get-refund",
+            "type": "text/html"
+        }
+    }
+}`
+
+// GetRefundListResponse example
+const GetRefundListResponse = `{
+    "count": 5,
+    "_embedded": {
+        "refunds": [
+            {
+                "resource": "refund",
+                "id": "re_4qqhO89gsT",
+                "amount": {
+                    "currency": "EUR",
+                    "value": "5.95"
+                },
+                "status": "pending",
+                "createdAt": "2018-03-14T17:09:02.0Z",
+                "description": "Order",
+                "metadata": {
+                     "bookkeeping_id": 12345
+                },
+                "paymentId": "tr_WDqYK6vllg",
+                "_links": {
+                    "self": {
+                        "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/refunds/re_4qqhO89gsT",
+                        "type": "application/hal+json"
+                    },
+                    "payment": {
+                        "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg",
+                        "type": "application/hal+json"
+                    },
+                    "documentation": {
+                        "href": "https://docs.mollie.com/reference/v2/refunds-api/get-refund",
+                        "type": "text/html"
+                    }
+                }
+            },
+            { },
+            { }
+        ]
+    },
+    "_links": {
+        "self": {
+            "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/refunds?limit=5",
+            "type": "application/hal+json"
+        },
+        "previous": null,
+        "next": {
+            "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/refunds?from=re_APBiGPH2vV&limit=5",
+            "type": "application/hal+json"
+        },
+        "documentation": {
+            "href": "https://docs.mollie.com/reference/v2/refunds-api/list-refunds",
+            "type": "text/html"
+        }
+    }
+}`