Repository: cloudflare/cloudflare-go (go)

Issue:
Lacking User billing history endpoint
### Current cloudflare-go version

0.29

### Description

Hi,

Thanks for a great library! Love it!

I've been looking for this endpoint `user/billing/history` since I'm creating an integration to track costs towards different services. I see that Billing profile was implemented in this issue: https://github.com/cloudflare/cloudflare-go/issues/31.



### Use cases

Gathering billing history to keep track of costs.

### Potential cloudflare-go usage

```go
-
```


### References

https://github.com/cloudflare/cloudflare-go/issues/31

Pull request description:
Add User Billing History
## Description

Adds in a function to the [user billing history endpoint](https://api.cloudflare.com/#user-billing-history). This is a part of #31. #99 only added one of the two endpoints.

Resolves #768 

## Has your change been tested?

Unit test has been created based off the example given in the API docs.

## Screenshots (if appropriate):

## Types of changes

What sort of change does your code introduce/modify?

- [ ] 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:

- [X] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [X] I have added tests to cover my changes.
- [X] All new and existing tests passed.
- [X] This change is using publicly documented (api.cloudflare.com or developers.cloudflare.com) and stable APIs.

Patch:
diff --git a/user.go b/user.go
index 50b7f2071..60012c690 100644
--- a/user.go
+++ b/user.go
@@ -60,6 +60,35 @@ type UserBillingProfile struct {
 	EditedOn        *time.Time `json:"edited_on,omitempty"`
 }
 
+type UserBillingHistoryResponse struct {
+	Response
+	Result     []UserBillingHistory `json:"result"`
+	ResultInfo ResultInfo           `json:"result_info"`
+}
+
+type UserBillingHistory struct {
+	ID          string                 `json:"id,omitempty"`
+	Type        string                 `json:"type,omitempty"`
+	Action      string                 `json:"action,omitempty"`
+	Description string                 `json:"description,omitempty"`
+	OccurredAt  *time.Time             `json:"occurred_at,omitempty"`
+	Amount      float32                `json:"amount,omitempty"`
+	Currency    string                 `json:"currency,omitempty"`
+	Zone        userBillingHistoryZone `json:"zone"`
+}
+
+type userBillingHistoryZone struct {
+	Name string `json:"name,omitempty"`
+}
+
+type UserBillingOptions struct {
+	PaginationOptions
+	Order      string     `url:"order,omitempty"`
+	Type       string     `url:"type,omitempty"`
+	OccurredAt *time.Time `url:"occurred_at,omitempty"`
+	Action     string     `url:"action,omitempty"`
+}
+
 // UserDetails provides information about the logged-in user.
 //
 // API reference: https://api.cloudflare.com/#user-user-details
@@ -113,3 +142,20 @@ func (api *API) UserBillingProfile(ctx context.Context) (UserBillingProfile, err
 
 	return r.Result, nil
 }
+
+// UserBillingHistory return the billing history of the user
+//
+// API reference: https://api.cloudflare.com/#user-billing-history-billing-history-details
+func (api *API) UserBillingHistory(ctx context.Context, pageOpts UserBillingOptions) ([]UserBillingHistory, error) {
+	uri := buildURI("/user/billing/history", pageOpts)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return []UserBillingHistory{}, err
+	}
+	var r UserBillingHistoryResponse
+	err = json.Unmarshal(res, &r)
+	if err != nil {
+		return []UserBillingHistory{}, errors.Wrap(err, errUnmarshalError)
+	}
+	return r.Result, nil
+}


Test patch:
diff --git a/user_test.go b/user_test.go
index 9a47c096f..021d747f9 100644
--- a/user_test.go
+++ b/user_test.go
@@ -195,3 +195,50 @@ func TestUser_UserBillingProfile(t *testing.T) {
 		assert.Equal(t, userBillingProfile, want, "structs not equal")
 	}
 }
+
+func TestUser_UserBillingHistory(t *testing.T) {
+	setup()
+	defer teardown()
+	mux.HandleFunc("/user/billing/history", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+  "success": true,
+  "errors": [],
+  "messages": [],
+  "result": [
+    {
+      "id": "b69a9f3492637782896352daae219e7d",
+      "type": "charge",
+      "action": "subscription",
+      "description": "The billing item description",
+      "occurred_at": "2014-03-01T12:21:59.3456Z",
+      "amount": 20.99,
+      "currency": "USD",
+      "zone": {
+        "name": "example.com"
+      }
+    }
+  ]
+}`)
+	})
+
+	userBillingProfile, err := client.UserBillingHistory(context.Background(), UserBillingOptions{})
+
+	OccurredAt, _ := time.Parse(time.RFC3339, "2014-03-01T12:21:59.3456Z")
+
+	want := []UserBillingHistory{{
+		ID:          "b69a9f3492637782896352daae219e7d",
+		Type:        "charge",
+		Action:      "subscription",
+		Description: "The billing item description",
+		OccurredAt:  &OccurredAt,
+		Amount:      20.99,
+		Currency:    "USD",
+		Zone:        userBillingHistoryZone{Name: "example.com"},
+	}}
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, userBillingProfile, want, "structs not equal")
+	}
+}