diff --git a/.travis.yml b/.travis.yml index 987acb9b8a..a16aac0664 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ cache: env: global: - - STRIPE_MOCK_VERSION=0.8.0 + - STRIPE_MOCK_VERSION=0.11.0 go: - "1.7" diff --git a/form/form.go b/form/form.go index 5fc5499680..a1aca8de26 100644 --- a/form/form.go +++ b/form/form.go @@ -190,7 +190,7 @@ func buildArrayOrSliceEncoder(t reflect.Type) encoderFunc { elemF(values, indexV, arrNames, indexV.Kind() == reflect.Ptr, nil) if isAppender(indexV.Type()) && !indexV.IsNil() { - v.Interface().(Appender).AppendTo(values, arrNames) + indexV.Interface().(Appender).AppendTo(values, arrNames) } } } diff --git a/plan.go b/plan.go index 2169ec3c0f..5f1cb4fbf2 100644 --- a/plan.go +++ b/plan.go @@ -1,24 +1,74 @@ package stripe +import ( + "strconv" + + "github.com/stripe/stripe-go/form" +) + // PlanInterval is the list of allowed values for a plan's interval. // Allowed values are "day", "week", "month", "year". type PlanInterval string +const ( + // PlanBillingSchemeTiered indicates that the price per single unit is tiered + // and can change with the total number of units. + PlanBillingSchemeTiered string = "tiered" + // PlanBillingSchemePerUnit indicates that each unit is billed at a fixed + // price. + PlanBillingSchemePerUnit string = "per_unit" +) + +const ( + // PlanUsageTypeLicensed indicates that the set Quantity on a subscription item + // will be used to bill for a subscription. + PlanUsageTypeLicensed string = "licensed" + // PlanUsageTypeMetered indicates that usage records must be reported instead + // of setting a Quantity on a subscription item. + PlanUsageTypeMetered string = "metered" +) + +const ( + // PlanTransformUsageModeRoundDown represents a bucket billing configuration: a partially + // filled bucket will count as an empty bucket. + PlanTransformUsageModeRoundDown string = "round_down" + // PlanTransformUsageModeRoundUp represents a bucket billing configuration: a partially + // filled bucket will count as a full bucket. + PlanTransformUsageModeRoundUp string = "round_up" +) + +// PlanTransformUsage represents the bucket billing configuration. +type PlanTransformUsage struct { + DivideBy int64 `json:"bucket_size"` + Round string `json:"round"` +} + +// PlanTier configures tiered pricing +type PlanTier struct { + Amount uint64 `json:"amount"` + UpTo uint64 `json:"up_to"` +} + // Plan is the resource representing a Stripe plan. // For more details see https://stripe.com/docs/api#plans. type Plan struct { - Amount uint64 `json:"amount"` - Created int64 `json:"created"` - Currency Currency `json:"currency"` - Deleted bool `json:"deleted"` - ID string `json:"id"` - Interval PlanInterval `json:"interval"` - IntervalCount uint64 `json:"interval_count"` - Live bool `json:"livemode"` - Meta map[string]string `json:"metadata"` - Nickname string `json:"nickname"` - Product string `json:"product"` - TrialPeriod uint64 `json:"trial_period_days"` + Amount uint64 `json:"amount"` + BillingScheme string `json:"billing_scheme"` + Created int64 `json:"created"` + Currency Currency `json:"currency"` + Deleted bool `json:"deleted"` + ID string `json:"id"` + Interval PlanInterval `json:"interval"` + IntervalCount uint64 `json:"interval_count"` + Live bool `json:"livemode"` + Meta map[string]string `json:"metadata"` + Nickname string `json:"nickname"` + Product string `json:"product"` + Tiers []*PlanTier `json:"tiers"` + TiersMode string `json:"tiers_mode"` + TransformUsage *PlanTransformUsage `json:"transform_usage"` + TrialPeriod uint64 `json:"trial_period_days"` + UsageType string `json:"usage_type"` } // PlanList is a list of plans as returned from a list endpoint. @@ -38,17 +88,45 @@ type PlanListParams struct { // PlanParams is the set of parameters that can be used when creating or updating a plan. // For more details see https://stripe.com/docs/api#create_plan and https://stripe.com/docs/api#update_plan. type PlanParams struct { - Params `form:"*"` - Amount uint64 `form:"amount"` - AmountZero bool `form:"amount,zero"` - Currency Currency `form:"currency"` - ID string `form:"id"` - Interval PlanInterval `form:"interval"` - IntervalCount uint64 `form:"interval_count"` - Nickname string `form:"nickname"` - Product *PlanProductParams `form:"product"` - ProductID *string `form:"product"` - TrialPeriod uint64 `form:"trial_period_days"` + Params `form:"*"` + Amount uint64 `form:"amount"` + AmountZero bool `form:"amount,zero"` + BillingScheme string `form:"billing_scheme"` + Currency Currency `form:"currency"` + ID string `form:"id"` + Interval PlanInterval `form:"interval"` + IntervalCount uint64 `form:"interval_count"` + Nickname string `form:"nickname"` + Product *PlanProductParams `form:"product"` + ProductID *string `form:"product"` + Tiers []*PlanTierParams `form:"tiers,indexed"` + TiersMode string `form:"tiers_mode"` + TransformUsage *PlanTransformUsageParams `form:"transform_usage"` + TrialPeriod uint64 `form:"trial_period_days"` + UsageType string `form:"usage_type"` +} + +// PlanTransformUsageParams represents the bucket billing configuration. +type PlanTransformUsageParams struct { + DivideBy int64 `form:"bucket_size"` + Round string `form:"round"` +} + +// PlanTierParams configures tiered pricing +type PlanTierParams struct { + Params `form:"*"` + Amount uint64 `form:"amount"` + UpTo uint64 `form:"-"` // handled in custom AppendTo + UpToInf bool `form:"-"` // handled in custom AppendTo +} + +// AppendTo implements custom up_to serialisation logic for tiers configuration +func (config *PlanTierParams) AppendTo(body *form.Values, keyParts []string) { + if config.UpToInf { + body.Add(form.FormatKey(append(keyParts, "up_to")), "inf") + } else { + body.Add(form.FormatKey(append(keyParts, "up_to")), strconv.FormatUint(config.UpTo, 10)) + } } // PlanProductParams is the set of parameters that can be used when creating a product inside a plan diff --git a/plan_test.go b/plan_test.go index 3de89acb77..dfaa7ad568 100644 --- a/plan_test.go +++ b/plan_test.go @@ -44,22 +44,34 @@ func TestPlanParams_AppendTo(t *testing.T) { Name: "Sapphire Elite", StatementDescriptor: "SAPPHIRE", } - productId := "prod_123abc" + productID := "prod_123abc" + tiers := []*PlanTierParams{{Amount: 123, UpTo: 321}, {Amount: 123, UpToInf: true}} testCases := []struct { field string params *PlanParams want interface{} }{ + {"amount", &PlanParams{}, ""}, + {"amount", &PlanParams{Amount: 0, AmountZero: false}, ""}, + {"amount", &PlanParams{Amount: 0, AmountZero: true}, strconv.FormatUint(0, 10)}, {"amount", &PlanParams{Amount: 123}, strconv.FormatUint(123, 10)}, {"currency", &PlanParams{Currency: "USD"}, "USD"}, {"id", &PlanParams{ID: "sapphire-elite"}, "sapphire-elite"}, - {"interval", &PlanParams{Interval: "month"}, "month"}, {"interval_count", &PlanParams{IntervalCount: 3}, strconv.FormatUint(3, 10)}, + {"interval", &PlanParams{Interval: "month"}, "month"}, + {"product", &PlanParams{ProductID: &productID}, "prod_123abc"}, {"product[id]", &PlanParams{Product: &productParams}, "ID"}, {"product[name]", &PlanParams{Product: &productParams}, "Sapphire Elite"}, {"product[statement_descriptor]", &PlanParams{Product: &productParams}, "SAPPHIRE"}, - {"product", &PlanParams{ProductID: &productId}, "prod_123abc"}, + {"tiers_mode", &PlanParams{TiersMode: "volume"}, "volume"}, + {"tiers[0][amount]", &PlanParams{Tiers: tiers}, strconv.FormatUint(123, 10)}, + {"tiers[0][up_to]", &PlanParams{Tiers: tiers}, strconv.FormatUint(321, 10)}, + {"tiers[1][amount]", &PlanParams{Tiers: tiers}, strconv.FormatUint(123, 10)}, + {"tiers[1][up_to]", &PlanParams{Tiers: tiers}, "inf"}, + {"transform_usage[bucket_size]", &PlanParams{TransformUsage: &PlanTransformUsageParams{DivideBy: 123, Round: "round_up"}}, strconv.FormatUint(123, 10)}, + {"transform_usage[round]", &PlanParams{TransformUsage: &PlanTransformUsageParams{DivideBy: 123, Round: "round_up"}}, "round_up"}, {"trial_period_days", &PlanParams{TrialPeriod: 123}, strconv.FormatUint(123, 10)}, + {"usage_type", &PlanParams{UsageType: "metered"}, "metered"}, } for _, tc := range testCases { t.Run(tc.field, func(t *testing.T) { diff --git a/scripts/check_gofmt.sh b/scripts/check_gofmt.sh index c634c94fec..4113d65fac 100755 --- a/scripts/check_gofmt.sh +++ b/scripts/check_gofmt.sh @@ -10,7 +10,7 @@ find_files() { bad_files=$(find_files | xargs gofmt -s -l) if [[ -n "${bad_files}" ]]; then - echo "!!! gofmt needs to be run on the following files: " + echo "!!! gofmt -s needs to be run on the following files: " echo "${bad_files}" exit 1 fi diff --git a/usage_record.go b/usage_record.go new file mode 100644 index 0000000000..d093dab302 --- /dev/null +++ b/usage_record.go @@ -0,0 +1,33 @@ +package stripe + +const ( + // UsageRecordParamsActionIncrement indicates that if two usage records conflict + // (i.E. are reported a the same timestamp), their Quantity will be summed + UsageRecordParamsActionIncrement string = "increment" + + // UsageRecordParamsActionSet indicates that if two usage records conflict + // (i.E. are reported a the same timestamp), the Quantity of the most recent + // usage record will overwrite any other quantity. + UsageRecordParamsActionSet string = "set" +) + +// UsageRecord represents a usage record. +// See https://stripe.com/docs/api#usage_records +type UsageRecord struct { + ID string `json:"id"` + Live bool `json:"livemode"` + Quantity uint64 `json:"quantity"` + SubscriptionItem string `json:"subscription_item"` + Timestamp uint64 `json:"timestamp"` +} + +// UsageRecordParams create a usage record for a specified subscription item +// and date, and fills it with a quantity. +type UsageRecordParams struct { + Params `form:"*"` + Action string `form:"action"` + Quantity uint64 `form:"quantity"` + QuantityZero bool `form:"quantity,zero"` + SubscriptionItem string `form:"-"` // passed in the URL + Timestamp uint64 `form:"timestamp"` +} diff --git a/usage_record_test.go b/usage_record_test.go new file mode 100644 index 0000000000..071a196981 --- /dev/null +++ b/usage_record_test.go @@ -0,0 +1,30 @@ +package stripe + +import ( + "strconv" + "testing" + + assert "github.com/stretchr/testify/require" + "github.com/stripe/stripe-go/form" +) + +func TestUsageRecordParams_AppendTo(t *testing.T) { + testCases := []struct { + field string + params *UsageRecordParams + want interface{} + }{ + {"quantity", &UsageRecordParams{Quantity: 2000}, strconv.FormatUint(2000, 10)}, + {"quantity", &UsageRecordParams{QuantityZero: true}, strconv.FormatUint(0, 10)}, + {"timestamp", &UsageRecordParams{Timestamp: 123123123}, strconv.FormatUint(123123123, 10)}, + {"action", &UsageRecordParams{Action: "increment"}, "increment"}, + } + for _, tc := range testCases { + t.Run(tc.field, func(t *testing.T) { + body := &form.Values{} + form.AppendTo(body, tc.params) + values := body.ToValues() + assert.Equal(t, tc.want, values.Get(tc.field)) + }) + } +} diff --git a/usagerecord/client.go b/usagerecord/client.go new file mode 100644 index 0000000000..ee4b82593c --- /dev/null +++ b/usagerecord/client.go @@ -0,0 +1,38 @@ +// Package usage_record provides the /subscription_items/{SUBSCRIPTION_ITEM_ID}/usage_records APIs +package usagerecord + +import ( + "fmt" + "net/url" + + stripe "github.com/stripe/stripe-go" + "github.com/stripe/stripe-go/form" +) + +// Client is used to invoke /plans APIs. +type Client struct { + B stripe.Backend + Key string +} + +// New creates a new usage record. +// For more details see https://stripe.com/docs/api#usage_records +func New(params *stripe.UsageRecordParams) (*stripe.UsageRecord, error) { + return getC().New(params) +} + +// New internal implementation to create a new usage record. +func (c Client) New(params *stripe.UsageRecordParams) (*stripe.UsageRecord, error) { + body := &form.Values{} + form.AppendTo(body, params) + + url := fmt.Sprintf("/subscription_items/%s/usage_records", url.QueryEscape(params.SubscriptionItem)) + record := &stripe.UsageRecord{} + err := c.B.Call("POST", url, c.Key, body, ¶ms.Params, record) + + return record, err +} + +func getC() Client { + return Client{stripe.GetBackend(stripe.APIBackend), stripe.Key} +} diff --git a/usagerecord/client_test.go b/usagerecord/client_test.go new file mode 100644 index 0000000000..64391b2fe3 --- /dev/null +++ b/usagerecord/client_test.go @@ -0,0 +1,22 @@ +package usagerecord + +import ( + "testing" + "time" + + assert "github.com/stretchr/testify/require" + stripe "github.com/stripe/stripe-go" + _ "github.com/stripe/stripe-go/testing" +) + +func TestUsageRecordNew(t *testing.T) { + now := uint64(time.Now().Unix()) + usageRecord, err := New(&stripe.UsageRecordParams{ + Quantity: 123, + Timestamp: now, + Action: stripe.UsageRecordParamsActionIncrement, + SubscriptionItem: "si_123", + }) + assert.Nil(t, err) + assert.NotNil(t, usageRecord) +}