diff --git a/billing.go b/billing.go new file mode 100644 index 00000000..ffd21668 --- /dev/null +++ b/billing.go @@ -0,0 +1,162 @@ +package clerk + +// Feature represents a feature associated with a plan. +type Feature struct { + APIResource + + Object string `json:"object"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Slug string `json:"slug"` + AvatarURL string `json:"avatar_url"` +} + +// BillingMoney represents money amounts with formatting. +type BillingMoney struct { + APIResource + + Amount int64 `json:"amount"` + AmountFormatted string `json:"amount_formatted"` + Currency string `json:"currency"` + CurrencySymbol string `json:"currency_symbol"` +} + +// BillingProduct represents a product. +type BillingProduct struct { + APIResource + + Object string `json:"object"` + ID string `json:"id"` + Slug string `json:"slug"` + Currency string `json:"currency"` + Name string `json:"name"` + IsDefault bool `json:"is_default"` + Plans []Plan `json:"plans"` +} + +// Plan represents a billing plan. +type Plan struct { + APIResource + + Object string `json:"object"` + ID string `json:"id"` + Name string `json:"name"` + Fee *BillingMoney `json:"fee"` + AnnualMonthlyFee *BillingMoney `json:"annual_monthly_fee"` + AnnualFee *BillingMoney `json:"annual_fee"` + Description string `json:"description"` + ProductID string `json:"product_id"` + Product *BillingProduct `json:"product"` + IsDefault bool `json:"is_default"` + IsRecurring bool `json:"is_recurring"` + PubliclyVisible bool `json:"publicly_visible"` + HasBaseFee bool `json:"has_base_fee"` + ForPayerType string `json:"for_payer_type"` + Slug string `json:"slug"` + AvatarURL string `json:"avatar_url"` + Features []Feature `json:"features"` + FreeTrialEnabled bool `json:"free_trial_enabled"` + FreeTrialDays *int `json:"free_trial_days"` +} + +// PlanList contains a list of plans. +type PlanList struct { + APIResource + + Data []Plan `json:"data"` + TotalCount int64 `json:"total_count"` +} + +// BillingPaymentMethod represents a payment method. +type BillingPaymentMethod struct { + APIResource + + Object string `json:"object"` + ID string `json:"id"` + PayerID string `json:"payer_id"` + PaymentMethod string `json:"payment_method"` + IsDefault *bool `json:"is_default"` + Gateway string `json:"gateway"` + GatewayExternalID string `json:"gateway_external_id"` + GatewayExternalAccountID *string `json:"gateway_external_account_id"` + Last4 string `json:"last4"` + Status string `json:"status"` + WalletType string `json:"wallet_type"` + CardType string `json:"card_type"` + ExpiryYear int `json:"expiry_year"` + ExpiryMonth int `json:"expiry_month"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + IsRemovable *bool `json:"is_removable"` +} + +// BillingSubscriptionItemNextPayment represents next payment info. +type BillingSubscriptionItemNextPayment struct { + APIResource + + Amount *BillingMoney `json:"amount"` + Date *int64 `json:"date"` +} + +// Payer represents a billing payer (user or organization). +type Payer struct { + APIResource + + Object string `json:"object"` + ID string `json:"id"` + InstanceID string `json:"instance_id"` + + // User payer only + UserID *string `json:"user_id"` + FirstName *string `json:"first_name"` + LastName *string `json:"last_name"` + Email *string `json:"email"` + + // Org payer only + OrganizationID *string `json:"organization_id"` + OrganizationName *string `json:"organization_name"` + + // Used for both org and user payers + ImageURL string `json:"image_url"` + + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +// SubscriptionItem represents a billing subscription item. +type SubscriptionItem struct { + APIResource + + Object string `json:"object"` + ID string `json:"id"` + InstanceID string `json:"instance_id"` + Status string `json:"status"` + PlanID *string `json:"plan_id"` + Plan *Plan `json:"plan"` + PlanPeriod string `json:"plan_period"` + PaymentMethodID string `json:"payment_method_id"` + PaymentMethod *BillingPaymentMethod `json:"payment_method"` + LifetimePaid *BillingMoney `json:"lifetime_paid"` + Amount *BillingMoney `json:"amount"` + NextPayment *BillingSubscriptionItemNextPayment `json:"next_payment"` + PayerID string `json:"payer_id"` + Payer *Payer `json:"payer"` + IsFreeTrial bool `json:"is_free_trial"` + PeriodStart int64 `json:"period_start"` + PeriodEnd *int64 `json:"period_end"` + ProrationDate string `json:"proration_date"` + CanceledAt *int64 `json:"canceled_at"` + PastDueAt *int64 `json:"past_due_at"` + EndedAt *int64 `json:"ended_at"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +// SubscriptionItemList contains a list of subscription items. +type SubscriptionItemList struct { + APIResource + + Data []SubscriptionItem `json:"data"` + TotalCount int64 `json:"total_count"` +} diff --git a/billing/api.go b/billing/api.go new file mode 100644 index 00000000..b2dffa3c --- /dev/null +++ b/billing/api.go @@ -0,0 +1,35 @@ +// Code generated by "gen"; DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. +package billing + +import ( + "context" + + "github.com/clerk/clerk-sdk-go/v2" +) + +// ListPlans returns a list of billing plans. +func ListPlans(ctx context.Context, params *ListPlansParams) (*clerk.PlanList, error) { + return getClient().ListPlans(ctx, params) +} + +// ListSubscriptionItems returns a list of subscription items. +func ListSubscriptionItems(ctx context.Context, params *ListSubscriptionItemsParams) (*clerk.SubscriptionItemList, error) { + return getClient().ListSubscriptionItems(ctx, params) +} + +// CancelSubscriptionItem cancels a subscription item. +func CancelSubscriptionItem(ctx context.Context, subscriptionItemID string, params *CancelSubscriptionItemParams) (*clerk.SubscriptionItem, error) { + return getClient().CancelSubscriptionItem(ctx, subscriptionItemID, params) +} + +// ExtendFreeTrial extends the free trial period of a subscription item. +func ExtendFreeTrial(ctx context.Context, subscriptionItemID string, params *ExtendFreeTrialParams) (*clerk.SubscriptionItem, error) { + return getClient().ExtendFreeTrial(ctx, subscriptionItemID, params) +} + +func getClient() *Client { + return &Client{ + Backend: clerk.GetBackend(), + } +} diff --git a/billing/client.go b/billing/client.go new file mode 100644 index 00000000..31c98bfd --- /dev/null +++ b/billing/client.go @@ -0,0 +1,162 @@ +// Package billing provides the Billing API. +package billing + +import ( + "context" + "net/http" + "net/url" + + "github.com/clerk/clerk-sdk-go/v2" +) + +//go:generate go run ../cmd/gen/main.go + +const path = "/billing" + +// Client is used to invoke the Billing API. +type Client struct { + Backend clerk.Backend +} + +func NewClient(config *clerk.ClientConfig) *Client { + return &Client{ + Backend: clerk.NewBackend(&config.BackendConfig), + } +} + +type ListPlansParams struct { + clerk.APIParams + clerk.ListParams + PayerType *string +} + +func (params *ListPlansParams) ToQuery() url.Values { + q := params.ListParams.ToQuery() + if params.PayerType != nil { + q.Set("payer_type", *params.PayerType) + } + return q +} + +// ListPlans returns a list of billing plans. +func (c *Client) ListPlans(ctx context.Context, params *ListPlansParams) (*clerk.PlanList, error) { + plansPath, err := clerk.JoinPath(path, "/plans") + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodGet, plansPath) + req.SetParams(params) + planList := &clerk.PlanList{} + err = c.Backend.Call(ctx, req, planList) + return planList, err +} + +type ListSubscriptionItemsParams struct { + clerk.APIParams + clerk.ListParams + Status *string + PayerType *string + PlanID *string + IncludeFree *bool + Query *string + UserID *string + OrganizationID *string +} + +func (params *ListSubscriptionItemsParams) ToQuery() url.Values { + q := params.ListParams.ToQuery() + if params.Status != nil { + q.Set("status", *params.Status) + } + if params.PayerType != nil { + q.Set("payer_type", *params.PayerType) + } + if params.PlanID != nil { + q.Set("plan_id", *params.PlanID) + } + if params.IncludeFree != nil { + if *params.IncludeFree { + q.Set("include_free", "true") + } else { + q.Set("include_free", "false") + } + } + if params.Query != nil { + q.Set("query", *params.Query) + } + if params.UserID != nil { + q.Set("user_id", *params.UserID) + } + if params.OrganizationID != nil { + q.Set("organization_id", *params.OrganizationID) + } + return q +} + +// ListSubscriptionItems returns a list of subscription items. +func (c *Client) ListSubscriptionItems(ctx context.Context, params *ListSubscriptionItemsParams) (*clerk.SubscriptionItemList, error) { + subscriptionItemsPath, err := clerk.JoinPath(path, "/subscription_items") + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodGet, subscriptionItemsPath) + req.SetParams(params) + subscriptionItemList := &clerk.SubscriptionItemList{} + err = c.Backend.Call(ctx, req, subscriptionItemList) + return subscriptionItemList, err +} + +type CancelSubscriptionItemParams struct { + clerk.APIParams + EndNow *bool +} + +func (params *CancelSubscriptionItemParams) ToQuery() url.Values { + q := url.Values{} + if params.EndNow != nil { + if *params.EndNow { + q.Set("end_now", "true") + } else { + q.Set("end_now", "false") + } + } + return q +} + +// CancelSubscriptionItem cancels a subscription item. +func (c *Client) CancelSubscriptionItem(ctx context.Context, subscriptionItemID string, params *CancelSubscriptionItemParams) (*clerk.SubscriptionItem, error) { + cancelPath, err := clerk.JoinPath(path, "/subscription_items", subscriptionItemID) + if err != nil { + return nil, err + } + + if params != nil { + query := params.ToQuery() + if len(query) > 0 { + cancelPath += "?" + query.Encode() + } + } + + req := clerk.NewAPIRequest(http.MethodDelete, cancelPath) + subscriptionItem := &clerk.SubscriptionItem{} + err = c.Backend.Call(ctx, req, subscriptionItem) + return subscriptionItem, err +} + +type ExtendFreeTrialParams struct { + clerk.APIParams + ExtendTo string `json:"extend_to"` +} + +// ExtendFreeTrial extends the free trial period of a subscription item. +func (c *Client) ExtendFreeTrial(ctx context.Context, subscriptionItemID string, params *ExtendFreeTrialParams) (*clerk.SubscriptionItem, error) { + extendPath, err := clerk.JoinPath(path, "/subscription_items", subscriptionItemID, "/extend_free_trial") + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodPost, extendPath) + req.SetParams(params) + subscriptionItem := &clerk.SubscriptionItem{} + err = c.Backend.Call(ctx, req, subscriptionItem) + return subscriptionItem, err +} diff --git a/billing/client_test.go b/billing/client_test.go new file mode 100644 index 00000000..41abccda --- /dev/null +++ b/billing/client_test.go @@ -0,0 +1,328 @@ +package billing + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "testing" + + "github.com/clerk/clerk-sdk-go/v2" + "github.com/clerk/clerk-sdk-go/v2/clerktest" + "github.com/stretchr/testify/require" +) + +func TestBillingClientListPlans(t *testing.T) { + t.Parallel() + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(`{"data": [{"object":"plan","id":"plan_123","name":"Basic Plan","for_payer_type":"user","features":[{"object":"feature","id":"feature_456","name":"Feature 1","key":"feature_1"}]}],"total_count": 1}`), + Method: http.MethodGet, + Path: "/v1/billing/plans", + Query: &url.Values{ + "limit": []string{"10"}, + "offset": []string{"0"}, + "payer_type": []string{"user"}, + }, + }, + } + client := NewClient(config) + params := &ListPlansParams{ + PayerType: clerk.String("user"), + } + params.Limit = clerk.Int64(10) + params.Offset = clerk.Int64(0) + planList, err := client.ListPlans(context.Background(), params) + require.NoError(t, err) + require.Equal(t, int64(1), planList.TotalCount) + require.Equal(t, 1, len(planList.Data)) + require.Equal(t, "plan_123", planList.Data[0].ID) + require.Equal(t, "Basic Plan", planList.Data[0].Name) + require.Equal(t, "user", planList.Data[0].ForPayerType) + require.Equal(t, 1, len(planList.Data[0].Features)) + require.Equal(t, "feature_456", planList.Data[0].Features[0].ID) + require.Equal(t, "Feature 1", planList.Data[0].Features[0].Name) +} + +func TestBillingClientListPlans_Error(t *testing.T) { + t.Parallel() + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Status: http.StatusBadRequest, + Out: json.RawMessage(`{ + "errors":[{ + "code":"list-plans-error-code" + }], + "clerk_trace_id":"list-plans-trace-id" +}`), + }, + } + client := NewClient(config) + _, err := client.ListPlans(context.Background(), &ListPlansParams{}) + require.Error(t, err) + apiErr, ok := err.(*clerk.APIErrorResponse) + require.True(t, ok) + require.Equal(t, "list-plans-trace-id", apiErr.TraceID) + require.Equal(t, 1, len(apiErr.Errors)) + require.Equal(t, "list-plans-error-code", apiErr.Errors[0].Code) +} + +func TestBillingClientListSubscriptionItems(t *testing.T) { + t.Parallel() + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(`{"data": [{"object":"subscription_item","id":"sub_item_123","payer_id":"payer_456","plan_id":"plan_789","status":"active","period_start":1640995200,"period_end":1643673600,"payer":{"object":"payer","id":"payer_456","user_id":"user_456","first_name":"John","last_name":"Doe","email":"john@example.com","created_at":1640995200,"updated_at":1640995200},"plan":{"object":"plan","id":"plan_789","name":"Pro Plan","payer_type":["user"],"features":[]},"created_at":1640995200,"updated_at":1640995200}],"total_count": 1}`), + Method: http.MethodGet, + Path: "/v1/billing/subscription_items", + Query: &url.Values{ + "limit": []string{"10"}, + "offset": []string{"0"}, + "user_id": []string{"user_456"}, + "status": []string{"active"}, + }, + }, + } + client := NewClient(config) + params := &ListSubscriptionItemsParams{ + UserID: clerk.String("user_456"), + Status: clerk.String("active"), + } + params.Limit = clerk.Int64(10) + params.Offset = clerk.Int64(0) + subscriptionItemList, err := client.ListSubscriptionItems(context.Background(), params) + require.NoError(t, err) + require.Equal(t, int64(1), subscriptionItemList.TotalCount) + require.Equal(t, 1, len(subscriptionItemList.Data)) + require.Equal(t, "sub_item_123", subscriptionItemList.Data[0].ID) + require.Equal(t, "payer_456", subscriptionItemList.Data[0].PayerID) + require.Equal(t, "plan_789", *subscriptionItemList.Data[0].PlanID) + require.Equal(t, "active", subscriptionItemList.Data[0].Status) + require.Equal(t, int64(1640995200), subscriptionItemList.Data[0].PeriodStart) + require.Equal(t, int64(1643673600), *subscriptionItemList.Data[0].PeriodEnd) + require.NotNil(t, subscriptionItemList.Data[0].Payer) + require.Equal(t, "payer_456", subscriptionItemList.Data[0].Payer.ID) + require.Equal(t, "John", *subscriptionItemList.Data[0].Payer.FirstName) + require.NotNil(t, subscriptionItemList.Data[0].Plan) + require.Equal(t, "plan_789", subscriptionItemList.Data[0].Plan.ID) + require.Equal(t, "Pro Plan", subscriptionItemList.Data[0].Plan.Name) +} + +func TestBillingClientListSubscriptionItems_Error(t *testing.T) { + t.Parallel() + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Status: http.StatusBadRequest, + Out: json.RawMessage(`{ + "errors":[{ + "code":"list-subscription-items-error-code" + }], + "clerk_trace_id":"list-subscription-items-trace-id" +}`), + }, + } + client := NewClient(config) + _, err := client.ListSubscriptionItems(context.Background(), &ListSubscriptionItemsParams{}) + require.Error(t, err) + apiErr, ok := err.(*clerk.APIErrorResponse) + require.True(t, ok) + require.Equal(t, "list-subscription-items-trace-id", apiErr.TraceID) + require.Equal(t, 1, len(apiErr.Errors)) + require.Equal(t, "list-subscription-items-error-code", apiErr.Errors[0].Code) +} + +func TestBillingClientCancelSubscriptionItem(t *testing.T) { + t.Parallel() + subscriptionItemID := "sub_item_123" + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(fmt.Sprintf(`{"object":"subscription_item","id":"%s","payer_id":"payer_456","plan_id":"plan_789","status":"canceled","period_start":1640995200,"period_end":1643673600,"created_at":1640995200,"updated_at":1640995260}`, subscriptionItemID)), + Method: http.MethodDelete, + Path: "/v1/billing/subscription_items/" + subscriptionItemID, + Query: &url.Values{ + "end_now": []string{"true"}, + }, + }, + } + client := NewClient(config) + subscriptionItem, err := client.CancelSubscriptionItem(context.Background(), subscriptionItemID, &CancelSubscriptionItemParams{ + EndNow: clerk.Bool(true), + }) + require.NoError(t, err) + require.Equal(t, subscriptionItemID, subscriptionItem.ID) + require.Equal(t, "canceled", subscriptionItem.Status) + require.Equal(t, int64(1640995260), subscriptionItem.UpdatedAt) +} + +func TestBillingClientCancelSubscriptionItem_Error(t *testing.T) { + t.Parallel() + subscriptionItemID := "sub_item_123" + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Status: http.StatusNotFound, + Out: json.RawMessage(`{ + "errors":[{ + "code":"subscription-item-not-found" + }], + "clerk_trace_id":"cancel-subscription-item-trace-id" +}`), + }, + } + client := NewClient(config) + _, err := client.CancelSubscriptionItem(context.Background(), subscriptionItemID, &CancelSubscriptionItemParams{}) + require.Error(t, err) + apiErr, ok := err.(*clerk.APIErrorResponse) + require.True(t, ok) + require.Equal(t, "cancel-subscription-item-trace-id", apiErr.TraceID) + require.Equal(t, 1, len(apiErr.Errors)) + require.Equal(t, "subscription-item-not-found", apiErr.Errors[0].Code) +} + +func TestBillingClientExtendFreeTrial(t *testing.T) { + t.Parallel() + subscriptionItemID := "sub_item_123" + extendTo := "2024-12-31T23:59:59Z" + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(fmt.Sprintf(`{"extend_to":"%s"}`, extendTo)), + Out: json.RawMessage(fmt.Sprintf(`{"object":"subscription_item","id":"%s","payer_id":"payer_456","plan_id":"plan_789","status":"trialing","period_start":1640995200,"period_end":1735689599,"created_at":1640995200,"updated_at":1640995260}`, subscriptionItemID)), + Method: http.MethodPost, + Path: "/v1/billing/subscription_items/" + subscriptionItemID + "/extend_free_trial", + }, + } + client := NewClient(config) + subscriptionItem, err := client.ExtendFreeTrial(context.Background(), subscriptionItemID, &ExtendFreeTrialParams{ + ExtendTo: extendTo, + }) + require.NoError(t, err) + require.Equal(t, subscriptionItemID, subscriptionItem.ID) + require.Equal(t, "trialing", subscriptionItem.Status) + require.Equal(t, int64(1735689599), *subscriptionItem.PeriodEnd) + require.Equal(t, int64(1640995260), subscriptionItem.UpdatedAt) +} + +func TestBillingClientExtendFreeTrial_Error(t *testing.T) { + t.Parallel() + subscriptionItemID := "sub_item_123" + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Status: http.StatusBadRequest, + Out: json.RawMessage(`{ + "errors":[{ + "code":"subscription-item-not-in-free-trial" + }], + "clerk_trace_id":"extend-free-trial-trace-id" +}`), + }, + } + client := NewClient(config) + _, err := client.ExtendFreeTrial(context.Background(), subscriptionItemID, &ExtendFreeTrialParams{ + ExtendTo: "2024-12-31T23:59:59Z", + }) + require.Error(t, err) + apiErr, ok := err.(*clerk.APIErrorResponse) + require.True(t, ok) + require.Equal(t, "extend-free-trial-trace-id", apiErr.TraceID) + require.Equal(t, 1, len(apiErr.Errors)) + require.Equal(t, "subscription-item-not-in-free-trial", apiErr.Errors[0].Code) +} + +func TestBillingClientCancelSubscriptionItemWithoutEndNow(t *testing.T) { + t.Parallel() + subscriptionItemID := "sub_item_123" + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(fmt.Sprintf(`{"object":"subscription_item","id":"%s","payer_id":"payer_456","plan_id":"plan_789","status":"canceled","period_start":1640995200,"period_end":1643673600,"created_at":1640995200,"updated_at":1640995260}`, subscriptionItemID)), + Method: http.MethodDelete, + Path: "/v1/billing/subscription_items/" + subscriptionItemID, + Query: &url.Values{ + "end_now": []string{"false"}, + }, + }, + } + client := NewClient(config) + subscriptionItem, err := client.CancelSubscriptionItem(context.Background(), subscriptionItemID, &CancelSubscriptionItemParams{ + EndNow: clerk.Bool(false), + }) + require.NoError(t, err) + require.Equal(t, subscriptionItemID, subscriptionItem.ID) + require.Equal(t, "canceled", subscriptionItem.Status) +} + +func TestBillingClientListPlansWithoutFilters(t *testing.T) { + t.Parallel() + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(`{"data": [{"object":"plan","id":"plan_123","name":"Basic Plan","for_payer_type":"user","features":[]},{"object":"plan","id":"plan_456","name":"Pro Plan","for_payer_type":"organization","features":[]}],"total_count": 2}`), + Method: http.MethodGet, + Path: "/v1/billing/plans", + }, + } + client := NewClient(config) + planList, err := client.ListPlans(context.Background(), &ListPlansParams{}) + require.NoError(t, err) + require.Equal(t, int64(2), planList.TotalCount) + require.Equal(t, 2, len(planList.Data)) + require.Equal(t, "plan_123", planList.Data[0].ID) + require.Equal(t, "Basic Plan", planList.Data[0].Name) + require.Equal(t, "user", planList.Data[0].ForPayerType) + require.Equal(t, "plan_456", planList.Data[1].ID) + require.Equal(t, "Pro Plan", planList.Data[1].Name) + require.Equal(t, "organization", planList.Data[1].ForPayerType) +} + +func TestBillingClientListSubscriptionItemsWithMultipleFilters(t *testing.T) { + t.Parallel() + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(`{"data": [{"object":"subscription_item","id":"sub_item_123","payer_id":"payer_456","plan_id":"plan_789","status":"active","period_start":1640995200,"period_end":1643673600,"payer":{"object":"payer","id":"payer_456","user_id":"user_456","first_name":"John","last_name":"Doe","email":"john@example.com","created_at":1640995200,"updated_at":1640995200},"plan":{"object":"plan","id":"plan_789","name":"Pro Plan","payer_type":["user"],"features":[]},"created_at":1640995200,"updated_at":1640995200}],"total_count": 1}`), + Method: http.MethodGet, + Path: "/v1/billing/subscription_items", + Query: &url.Values{ + "user_id": []string{"user_456"}, + "plan_id": []string{"plan_789"}, + "status": []string{"active"}, + "payer_type": []string{"user"}, + "include_free": []string{"false"}, + }, + }, + } + client := NewClient(config) + params := &ListSubscriptionItemsParams{ + UserID: clerk.String("user_456"), + PlanID: clerk.String("plan_789"), + Status: clerk.String("active"), + PayerType: clerk.String("user"), + IncludeFree: clerk.Bool(false), + } + subscriptionItemList, err := client.ListSubscriptionItems(context.Background(), params) + require.NoError(t, err) + require.Equal(t, int64(1), subscriptionItemList.TotalCount) + require.Equal(t, 1, len(subscriptionItemList.Data)) + require.Equal(t, "sub_item_123", subscriptionItemList.Data[0].ID) + require.Equal(t, "payer_456", subscriptionItemList.Data[0].PayerID) + require.Equal(t, "plan_789", *subscriptionItemList.Data[0].PlanID) + require.Equal(t, "active", subscriptionItemList.Data[0].Status) +}