ducky/subscriptions

Paddy 2015-09-27 Parent:c4cfceb2f2fb

13:1ff031bebf9e Go to Latest

ducky/subscriptions/stripe.go

Add golint comments. Comment on some more of our exported types, functions, and variables, both to make golint happy and because uncommented code never ever ends well.

History
paddy@2 1 package subscriptions
paddy@2 2
paddy@2 3 import (
paddy@6 4 "errors"
paddy@2 5 "time"
paddy@2 6
paddy@2 7 "code.secondbit.org/uuid.hg"
paddy@2 8
paddy@2 9 "github.com/stripe/stripe-go"
paddy@2 10 "github.com/stripe/stripe-go/customer"
paddy@2 11 "github.com/stripe/stripe-go/sub"
paddy@2 12 )
paddy@2 13
paddy@6 14 const (
paddy@13 15 // PendingPlan holds the name for the plan users that haven't chosen a plan are subscribed to.
paddy@6 16 PendingPlan = "pending"
paddy@6 17 )
paddy@6 18
paddy@6 19 var (
paddy@13 20 // ErrNilCustomer is returned when a *Customer is expected, but nil is passed.
paddy@13 21 ErrNilCustomer = errors.New("nil customer passed")
paddy@13 22 // ErrNilCustomerSubs is returned when *Customer.Subs is expected to be a slice, but is nil.
paddy@13 23 ErrNilCustomerSubs = errors.New("customer with nil subscriptions list passed")
paddy@13 24 // ErrWrongNumberOfCustomerSubs is returned when a *Customer.Subs slice has more or fewer elements than expected.
paddy@6 25 ErrWrongNumberOfCustomerSubs = errors.New("customer with wrong number of subscriptions passed")
paddy@13 26 // ErrNilSubscription is returned when a *Subscription is expected, but nil is passed.
paddy@13 27 ErrNilSubscription = errors.New("nil subscription passed")
paddy@6 28 )
paddy@6 29
paddy@13 30 // Stripe is a wrapper around the clients for Stripe that we're using. It's responsible for
paddy@13 31 // all the interaction with the Stripe API that we're doing. It is a level higher than the
paddy@13 32 // raw Stripe clients, and can be thought of as Stripe-from-the-perspective-of-subscriptions.
paddy@2 33 type Stripe struct {
paddy@2 34 apiKey string
paddy@2 35 customers customer.Client
paddy@2 36 subscriptions sub.Client
paddy@2 37 }
paddy@2 38
paddy@13 39 // NewStripe creates a new Stripe instance using the passed parameters. The stripe.Backend
paddy@13 40 // parameter can be used to create a stub instead of something that actually hits the Stripe
paddy@13 41 // API. See the github.com/stripe/stripe-go documentation for more information about Backends.
paddy@2 42 func NewStripe(apiKey string, backend stripe.Backend) Stripe {
paddy@2 43 return Stripe{
paddy@2 44 apiKey: apiKey,
paddy@2 45 customers: customer.Client{
paddy@2 46 B: backend,
paddy@2 47 Key: apiKey,
paddy@2 48 },
paddy@2 49 subscriptions: sub.Client{
paddy@2 50 B: backend,
paddy@2 51 Key: apiKey,
paddy@2 52 },
paddy@2 53 }
paddy@2 54 }
paddy@2 55
paddy@13 56 // CreateStripeCustomer sets up a customer using the passed Stripe client, ensuring it follows all
paddy@13 57 // the conventions that subscriptions expects (the description being set properly, the UserID meta,
paddy@13 58 // etc.) Creating a customer automatically creates a subscription for that customer.
paddy@6 59 func CreateStripeCustomer(plan, email string, userID uuid.ID, s Stripe) (*stripe.Customer, error) {
paddy@2 60 customerParams := &stripe.CustomerParams{
paddy@2 61 Desc: "Customer for user " + userID.String(),
paddy@2 62 Email: email,
paddy@6 63 Plan: plan,
paddy@2 64 }
paddy@2 65 customerParams.AddMeta("UserID", userID.String())
paddy@2 66 c, err := s.customers.New(customerParams)
paddy@2 67 if err != nil {
paddy@2 68 return nil, err
paddy@2 69 }
paddy@2 70 return c, nil
paddy@2 71 }
paddy@2 72
paddy@13 73 // UpdateStripeSubscription modifies a subscription in Stripe, updating the plan and/or token.
paddy@13 74 // A nil plan or token indicates no change to the parameter.
paddy@6 75 func UpdateStripeSubscription(customerID string, plan, token *string, s Stripe) (*stripe.Sub, error) {
paddy@6 76 params := &stripe.SubParams{}
paddy@6 77 if plan != nil {
paddy@6 78 params.Plan = *plan
paddy@2 79 }
paddy@6 80 if token != nil {
paddy@6 81 params.Token = *token
paddy@6 82 }
paddy@6 83 subscription, err := s.subscriptions.Update(customerID, params)
paddy@2 84 if err != nil {
paddy@2 85 return nil, err
paddy@2 86 }
paddy@6 87 return subscription, nil
paddy@2 88 }
paddy@2 89
paddy@6 90 // New should be called when a user's profile is created. At this point, we know nothing about the subscription
paddy@6 91 // they actually _want_. We just sign them up for the dedicated "pending" plan. This is to make their free trial begin
paddy@6 92 // immediately and not have to worry about automatically locking them out until they actually create a subscription.
paddy@6 93 // Basically, we want everyone to have a subscription at all times, but some users will have placeholders until they
paddy@6 94 // actually update their subscription with a desired plan and payment method.
paddy@6 95 func New(req SubscriptionChange, s Stripe, store SubscriptionStore) (Subscription, error) {
paddy@6 96 subscription := Subscription{}
paddy@6 97 subscription.ApplyChange(req)
paddy@6 98 // BUG(paddy): need to validate the change
paddy@6 99
paddy@6 100 // create the customer in Stripe, storing the token for reuse
paddy@6 101 customer, err := CreateStripeCustomer(PendingPlan, *req.Email, req.UserID, s)
paddy@6 102 if err != nil {
paddy@6 103 return subscription, err
paddy@6 104 }
paddy@6 105 if customer == nil {
paddy@6 106 return subscription, ErrNilCustomer
paddy@6 107 }
paddy@6 108 if customer.Subs == nil {
paddy@6 109 return subscription, ErrNilCustomerSubs
paddy@6 110 }
paddy@6 111 if len(customer.Subs.Values) != 1 {
paddy@6 112 return subscription, ErrWrongNumberOfCustomerSubs
paddy@6 113 }
paddy@6 114 if customer.Subs.Values[0] == nil {
paddy@6 115 return subscription, ErrNilSubscription
paddy@6 116 }
paddy@6 117
paddy@6 118 change := StripeSubscriptionChange(subscription, *customer.Subs.Values[0])
paddy@6 119 subscription.ApplyChange(change)
paddy@6 120
paddy@6 121 err = store.CreateSubscription(subscription)
paddy@2 122 if err != nil {
paddy@3 123 return subscription, err
paddy@2 124 }
paddy@2 125
paddy@3 126 return subscription, nil
paddy@2 127 }
paddy@2 128
paddy@13 129 // StripeSubscriptionChange takes a Subscription and a stripe.Sub, and returns a SubscriptionChange
paddy@13 130 // that will change only the properties necessary to make the Subscription match the stripe.Sub.
paddy@2 131 func StripeSubscriptionChange(orig Subscription, subscription stripe.Sub) SubscriptionChange {
paddy@2 132 var change SubscriptionChange
paddy@6 133 if subscription.ID != orig.StripeSubscription {
paddy@6 134 change.StripeSubscription = &subscription.ID
paddy@6 135 }
paddy@2 136 if subscription.Plan != nil && orig.Plan != subscription.Plan.ID {
paddy@2 137 change.Plan = &subscription.Plan.ID
paddy@2 138 }
paddy@2 139 if string(subscription.Status) != orig.Status {
paddy@2 140 status := string(subscription.Status)
paddy@2 141 change.Status = &status
paddy@2 142 }
paddy@2 143 if subscription.EndCancel != orig.Canceling {
paddy@2 144 change.Canceling = &subscription.EndCancel
paddy@2 145 }
paddy@3 146 if !time.Unix(subscription.TrialStart, 0).Equal(orig.TrialStart) && !(subscription.TrialStart == 0 && orig.TrialStart.IsZero()) {
paddy@2 147 trialStart := time.Unix(subscription.TrialStart, 0)
paddy@2 148 change.TrialStart = &trialStart
paddy@2 149 }
paddy@3 150 if !time.Unix(subscription.TrialEnd, 0).Equal(orig.TrialEnd) && !(subscription.TrialEnd == 0 && orig.TrialEnd.IsZero()) {
paddy@2 151 trialEnd := time.Unix(subscription.TrialEnd, 0)
paddy@2 152 change.TrialEnd = &trialEnd
paddy@2 153 }
paddy@3 154 if !time.Unix(subscription.PeriodStart, 0).Equal(orig.PeriodStart) && !(subscription.PeriodStart == 0 && orig.PeriodStart.IsZero()) {
paddy@2 155 periodStart := time.Unix(subscription.PeriodStart, 0)
paddy@2 156 change.PeriodStart = &periodStart
paddy@2 157 }
paddy@3 158 if !time.Unix(subscription.PeriodEnd, 0).Equal(orig.PeriodEnd) && !(subscription.PeriodEnd == 0 && orig.PeriodEnd.IsZero()) {
paddy@2 159 periodEnd := time.Unix(subscription.PeriodEnd, 0)
paddy@2 160 change.PeriodEnd = &periodEnd
paddy@2 161 }
paddy@3 162 if !time.Unix(subscription.Canceled, 0).Equal(orig.CanceledAt) && !(subscription.Canceled == 0 && orig.CanceledAt.IsZero()) {
paddy@2 163 canceledAt := time.Unix(subscription.Canceled, 0)
paddy@2 164 change.CanceledAt = &canceledAt
paddy@2 165 }
paddy@2 166 return change
paddy@2 167 }