Skip to content

Commit a2e5acc

Browse files
feat: Add new enhanced billing endpoints (#3605)
1 parent 5862e24 commit a2e5acc

File tree

4 files changed

+533
-0
lines changed

4 files changed

+533
-0
lines changed

github/billing.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,46 @@ type AdvancedSecurityCommittersBreakdown struct {
6363
LastPushedDate *string `json:"last_pushed_date,omitempty"`
6464
}
6565

66+
// UsageReportOptions specifies optional parameters for the enhanced billing platform usage report.
67+
type UsageReportOptions struct {
68+
// If specified, only return results for a single year. The value of year is an integer with four digits representing a year. For example, 2025.
69+
// Default value is the current year.
70+
Year *int `url:"year,omitempty"`
71+
72+
// If specified, only return results for a single month. The value of month is an integer between 1 and 12.
73+
// If no year is specified the default year is used.
74+
Month *int `url:"month,omitempty"`
75+
76+
// If specified, only return results for a single day. The value of day is an integer between 1 and 31.
77+
// If no year or month is specified, the default year and month are used.
78+
Day *int `url:"day,omitempty"`
79+
80+
// If specified, only return results for a single hour. The value of hour is an integer between 0 and 23.
81+
// If no year, month, or day is specified, the default year, month, and day are used.
82+
Hour *int `url:"hour,omitempty"`
83+
}
84+
85+
// UsageItem represents a single usage item in the enhanced billing platform report.
86+
type UsageItem struct {
87+
Date *string `json:"date"`
88+
Product *string `json:"product"`
89+
SKU *string `json:"sku"`
90+
Quantity *int `json:"quantity"`
91+
UnitType *string `json:"unitType"`
92+
PricePerUnit *float64 `json:"pricePerUnit"`
93+
GrossAmount *float64 `json:"grossAmount"`
94+
DiscountAmount *float64 `json:"discountAmount"`
95+
NetAmount *float64 `json:"netAmount"`
96+
RepositoryName *string `json:"repositoryName,omitempty"`
97+
// Organization name is only used for organization-level reports.
98+
OrganizationName *string `json:"organizationName,omitempty"`
99+
}
100+
101+
// UsageReport represents the enhanced billing platform usage report response.
102+
type UsageReport struct {
103+
UsageItems []*UsageItem `json:"usageItems,omitempty"`
104+
}
105+
66106
// GetActionsBillingOrg returns the summary of the free and paid GitHub Actions minutes used for an Org.
67107
//
68108
// GitHub API docs: https://docs.github.com/rest/billing/billing#get-github-actions-billing-for-an-organization
@@ -216,3 +256,59 @@ func (s *BillingService) GetStorageBillingUser(ctx context.Context, user string)
216256

217257
return storageUserBilling, resp, nil
218258
}
259+
260+
// GetUsageReportOrg returns a report of the total usage for an organization using the enhanced billing platform.
261+
//
262+
// Note: This endpoint is only available to organizations with access to the enhanced billing platform.
263+
//
264+
// GitHub API docs: https://docs.github.com/rest/billing/enhanced-billing#get-billing-usage-report-for-an-organization
265+
//
266+
//meta:operation GET /organizations/{org}/settings/billing/usage
267+
func (s *BillingService) GetUsageReportOrg(ctx context.Context, org string, opts *UsageReportOptions) (*UsageReport, *Response, error) {
268+
u := fmt.Sprintf("organizations/%v/settings/billing/usage", org)
269+
u, err := addOptions(u, opts)
270+
if err != nil {
271+
return nil, nil, err
272+
}
273+
274+
req, err := s.client.NewRequest("GET", u, nil)
275+
if err != nil {
276+
return nil, nil, err
277+
}
278+
279+
usageReport := new(UsageReport)
280+
resp, err := s.client.Do(ctx, req, usageReport)
281+
if err != nil {
282+
return nil, resp, err
283+
}
284+
285+
return usageReport, resp, nil
286+
}
287+
288+
// GetUsageReportUser returns a report of the total usage for a user using the enhanced billing platform.
289+
//
290+
// Note: This endpoint is only available to users with access to the enhanced billing platform.
291+
//
292+
// GitHub API docs: https://docs.github.com/rest/billing/enhanced-billing#get-billing-usage-report-for-a-user
293+
//
294+
//meta:operation GET /users/{username}/settings/billing/usage
295+
func (s *BillingService) GetUsageReportUser(ctx context.Context, user string, opts *UsageReportOptions) (*UsageReport, *Response, error) {
296+
u := fmt.Sprintf("users/%v/settings/billing/usage", user)
297+
u, err := addOptions(u, opts)
298+
if err != nil {
299+
return nil, nil, err
300+
}
301+
302+
req, err := s.client.NewRequest("GET", u, nil)
303+
if err != nil {
304+
return nil, nil, err
305+
}
306+
307+
usageReport := new(UsageReport)
308+
resp, err := s.client.Do(ctx, req, usageReport)
309+
if err != nil {
310+
return nil, resp, err
311+
}
312+
313+
return usageReport, resp, nil
314+
}

github/billing_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,3 +510,167 @@ func TestBillingService_GetAdvancedSecurityActiveCommittersOrg_invalidOrg(t *tes
510510
_, _, err := client.Billing.GetAdvancedSecurityActiveCommittersOrg(ctx, "%", nil)
511511
testURLParseError(t, err)
512512
}
513+
514+
func TestBillingService_GetUsageReportOrg(t *testing.T) {
515+
t.Parallel()
516+
client, mux, _ := setup(t)
517+
518+
mux.HandleFunc("/organizations/o/settings/billing/usage", func(w http.ResponseWriter, r *http.Request) {
519+
testMethod(t, r, "GET")
520+
testFormValues(t, r, values{
521+
"year": "2023",
522+
"month": "8",
523+
})
524+
fmt.Fprint(w, `{
525+
"usageItems": [
526+
{
527+
"date": "2023-08-01",
528+
"product": "Actions",
529+
"sku": "Actions Linux",
530+
"quantity": 100,
531+
"unitType": "minutes",
532+
"pricePerUnit": 0.008,
533+
"grossAmount": 0.8,
534+
"discountAmount": 0,
535+
"netAmount": 0.8,
536+
"organizationName": "GitHub",
537+
"repositoryName": "github/example"
538+
}
539+
]
540+
}`)
541+
})
542+
543+
ctx := context.Background()
544+
opts := &UsageReportOptions{
545+
Year: Ptr(2023),
546+
Month: Ptr(8),
547+
}
548+
report, _, err := client.Billing.GetUsageReportOrg(ctx, "o", opts)
549+
if err != nil {
550+
t.Errorf("Billing.GetUsageReportOrg returned error: %v", err)
551+
}
552+
553+
want := &UsageReport{
554+
UsageItems: []*UsageItem{
555+
{
556+
Date: Ptr("2023-08-01"),
557+
Product: Ptr("Actions"),
558+
SKU: Ptr("Actions Linux"),
559+
Quantity: Ptr(100),
560+
UnitType: Ptr("minutes"),
561+
PricePerUnit: Ptr(0.008),
562+
GrossAmount: Ptr(0.8),
563+
DiscountAmount: Ptr(0.0),
564+
NetAmount: Ptr(0.8),
565+
OrganizationName: Ptr("GitHub"),
566+
RepositoryName: Ptr("github/example"),
567+
},
568+
},
569+
}
570+
if !cmp.Equal(report, want) {
571+
t.Errorf("Billing.GetUsageReportOrg returned %+v, want %+v", report, want)
572+
}
573+
574+
const methodName = "GetUsageReportOrg"
575+
testBadOptions(t, methodName, func() (err error) {
576+
_, _, err = client.Billing.GetUsageReportOrg(ctx, "\n", opts)
577+
return err
578+
})
579+
580+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
581+
got, resp, err := client.Billing.GetUsageReportOrg(ctx, "o", nil)
582+
if got != nil {
583+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
584+
}
585+
return resp, err
586+
})
587+
}
588+
589+
func TestBillingService_GetUsageReportOrg_invalidOrg(t *testing.T) {
590+
t.Parallel()
591+
client, _, _ := setup(t)
592+
593+
ctx := context.Background()
594+
_, _, err := client.Billing.GetUsageReportOrg(ctx, "%", nil)
595+
testURLParseError(t, err)
596+
}
597+
598+
func TestBillingService_GetUsageReportUser(t *testing.T) {
599+
t.Parallel()
600+
client, mux, _ := setup(t)
601+
602+
mux.HandleFunc("/users/u/settings/billing/usage", func(w http.ResponseWriter, r *http.Request) {
603+
testMethod(t, r, "GET")
604+
testFormValues(t, r, values{
605+
"day": "15",
606+
})
607+
fmt.Fprint(w, `{
608+
"usageItems": [
609+
{
610+
"date": "2023-08-15",
611+
"product": "Codespaces",
612+
"sku": "Codespaces Linux",
613+
"quantity": 50,
614+
"unitType": "hours",
615+
"pricePerUnit": 0.18,
616+
"grossAmount": 9.0,
617+
"discountAmount": 1.0,
618+
"netAmount": 8.0,
619+
"repositoryName": "user/example"
620+
}
621+
]
622+
}`)
623+
})
624+
625+
ctx := context.Background()
626+
opts := &UsageReportOptions{
627+
Day: Ptr(15),
628+
}
629+
report, _, err := client.Billing.GetUsageReportUser(ctx, "u", opts)
630+
if err != nil {
631+
t.Errorf("Billing.GetUsageReportUser returned error: %v", err)
632+
}
633+
634+
want := &UsageReport{
635+
UsageItems: []*UsageItem{
636+
{
637+
Date: Ptr("2023-08-15"),
638+
Product: Ptr("Codespaces"),
639+
SKU: Ptr("Codespaces Linux"),
640+
Quantity: Ptr(50),
641+
UnitType: Ptr("hours"),
642+
PricePerUnit: Ptr(0.18),
643+
GrossAmount: Ptr(9.0),
644+
DiscountAmount: Ptr(1.0),
645+
NetAmount: Ptr(8.0),
646+
RepositoryName: Ptr("user/example"),
647+
},
648+
},
649+
}
650+
if !cmp.Equal(report, want) {
651+
t.Errorf("Billing.GetUsageReportUser returned %+v, want %+v", report, want)
652+
}
653+
654+
const methodName = "GetUsageReportUser"
655+
testBadOptions(t, methodName, func() (err error) {
656+
_, _, err = client.Billing.GetUsageReportUser(ctx, "\n", opts)
657+
return err
658+
})
659+
660+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
661+
got, resp, err := client.Billing.GetUsageReportUser(ctx, "u", nil)
662+
if got != nil {
663+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
664+
}
665+
return resp, err
666+
})
667+
}
668+
669+
func TestBillingService_GetUsageReportUser_invalidUser(t *testing.T) {
670+
t.Parallel()
671+
client, _, _ := setup(t)
672+
673+
ctx := context.Background()
674+
_, _, err := client.Billing.GetUsageReportUser(ctx, "%", nil)
675+
testURLParseError(t, err)
676+
}

0 commit comments

Comments
 (0)