Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions billing.go
Original file line number Diff line number Diff line change
@@ -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"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we always have a Fee and only sometimes have an annual fee? Or are there times where we may not have a fee, like the default free plan? 🤔

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"`
Comment thread
paddycarver marked this conversation as resolved.
IsRemovable *bool `json:"is_removable"`
}

// BillingSubscriptionItemNextPayment represents next payment info.
type BillingSubscriptionItemNextPayment struct {
APIResource

Amount *BillingMoney `json:"amount"`
Date *int64 `json:"date"`
Comment on lines +98 to +99
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will these items ever not be set when the outer struct is present?

}

// 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"`
}
35 changes: 35 additions & 0 deletions billing/api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

162 changes: 162 additions & 0 deletions billing/client.go
Original file line number Diff line number Diff line change
@@ -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()
Comment thread
mauricioabreu marked this conversation as resolved.
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()
Comment thread
mauricioabreu marked this conversation as resolved.
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{}
Comment thread
mauricioabreu marked this conversation as resolved.
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"`
}
Comment thread
mauricioabreu marked this conversation as resolved.

// 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
}
Loading
Loading