ducky/subscriptions
ducky/subscriptions/stripe.go
Add comments, move ChangingSystemProperties to the api package. Add comments to all our exported types and variables in subscription.go, both to make golint happy and because it's good to have comments. Move the subscriptions.ChangingSystemProperties helper to api.changingSystemProperties, because it returns API-specific strings and there's no real reason it has to be in the subscriptions package--everything it needs to work on is exported.
| 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@6 | 15 PendingPlan = "pending" |
| paddy@6 | 16 ) |
| paddy@6 | 17 |
| paddy@6 | 18 var ( |
| paddy@6 | 19 ErrNilCustomer = errors.New("nil customer passed") |
| paddy@6 | 20 ErrNilCustomerSubs = errors.New("customer with nil subscriptions list passed") |
| paddy@6 | 21 ErrWrongNumberOfCustomerSubs = errors.New("customer with wrong number of subscriptions passed") |
| paddy@6 | 22 ErrNilSubscription = errors.New("nil subscription passed") |
| paddy@6 | 23 ) |
| paddy@6 | 24 |
| paddy@2 | 25 type Stripe struct { |
| paddy@2 | 26 apiKey string |
| paddy@2 | 27 customers customer.Client |
| paddy@2 | 28 subscriptions sub.Client |
| paddy@2 | 29 } |
| paddy@2 | 30 |
| paddy@2 | 31 func NewStripe(apiKey string, backend stripe.Backend) Stripe { |
| paddy@2 | 32 return Stripe{ |
| paddy@2 | 33 apiKey: apiKey, |
| paddy@2 | 34 customers: customer.Client{ |
| paddy@2 | 35 B: backend, |
| paddy@2 | 36 Key: apiKey, |
| paddy@2 | 37 }, |
| paddy@2 | 38 subscriptions: sub.Client{ |
| paddy@2 | 39 B: backend, |
| paddy@2 | 40 Key: apiKey, |
| paddy@2 | 41 }, |
| paddy@2 | 42 } |
| paddy@2 | 43 } |
| paddy@2 | 44 |
| paddy@6 | 45 func CreateStripeCustomer(plan, email string, userID uuid.ID, s Stripe) (*stripe.Customer, error) { |
| paddy@2 | 46 customerParams := &stripe.CustomerParams{ |
| paddy@2 | 47 Desc: "Customer for user " + userID.String(), |
| paddy@2 | 48 Email: email, |
| paddy@6 | 49 Plan: plan, |
| paddy@2 | 50 } |
| paddy@2 | 51 customerParams.AddMeta("UserID", userID.String()) |
| paddy@2 | 52 c, err := s.customers.New(customerParams) |
| paddy@2 | 53 if err != nil { |
| paddy@2 | 54 return nil, err |
| paddy@2 | 55 } |
| paddy@2 | 56 return c, nil |
| paddy@2 | 57 } |
| paddy@2 | 58 |
| paddy@6 | 59 func UpdateStripeSubscription(customerID string, plan, token *string, s Stripe) (*stripe.Sub, error) { |
| paddy@6 | 60 params := &stripe.SubParams{} |
| paddy@6 | 61 if plan != nil { |
| paddy@6 | 62 params.Plan = *plan |
| paddy@2 | 63 } |
| paddy@6 | 64 if token != nil { |
| paddy@6 | 65 params.Token = *token |
| paddy@6 | 66 } |
| paddy@6 | 67 subscription, err := s.subscriptions.Update(customerID, params) |
| paddy@2 | 68 if err != nil { |
| paddy@2 | 69 return nil, err |
| paddy@2 | 70 } |
| paddy@6 | 71 return subscription, nil |
| paddy@2 | 72 } |
| paddy@2 | 73 |
| paddy@6 | 74 // New should be called when a user's profile is created. At this point, we know nothing about the subscription |
| paddy@6 | 75 // they actually _want_. We just sign them up for the dedicated "pending" plan. This is to make their free trial begin |
| paddy@6 | 76 // immediately and not have to worry about automatically locking them out until they actually create a subscription. |
| paddy@6 | 77 // Basically, we want everyone to have a subscription at all times, but some users will have placeholders until they |
| paddy@6 | 78 // actually update their subscription with a desired plan and payment method. |
| paddy@6 | 79 func New(req SubscriptionChange, s Stripe, store SubscriptionStore) (Subscription, error) { |
| paddy@6 | 80 subscription := Subscription{} |
| paddy@6 | 81 subscription.ApplyChange(req) |
| paddy@6 | 82 // BUG(paddy): need to validate the change |
| paddy@6 | 83 |
| paddy@6 | 84 // create the customer in Stripe, storing the token for reuse |
| paddy@6 | 85 customer, err := CreateStripeCustomer(PendingPlan, *req.Email, req.UserID, s) |
| paddy@6 | 86 if err != nil { |
| paddy@6 | 87 return subscription, err |
| paddy@6 | 88 } |
| paddy@6 | 89 if customer == nil { |
| paddy@6 | 90 return subscription, ErrNilCustomer |
| paddy@6 | 91 } |
| paddy@6 | 92 if customer.Subs == nil { |
| paddy@6 | 93 return subscription, ErrNilCustomerSubs |
| paddy@6 | 94 } |
| paddy@6 | 95 if len(customer.Subs.Values) != 1 { |
| paddy@6 | 96 return subscription, ErrWrongNumberOfCustomerSubs |
| paddy@6 | 97 } |
| paddy@6 | 98 if customer.Subs.Values[0] == nil { |
| paddy@6 | 99 return subscription, ErrNilSubscription |
| paddy@6 | 100 } |
| paddy@6 | 101 |
| paddy@6 | 102 change := StripeSubscriptionChange(subscription, *customer.Subs.Values[0]) |
| paddy@6 | 103 subscription.ApplyChange(change) |
| paddy@6 | 104 |
| paddy@6 | 105 err = store.CreateSubscription(subscription) |
| paddy@2 | 106 if err != nil { |
| paddy@3 | 107 return subscription, err |
| paddy@2 | 108 } |
| paddy@2 | 109 |
| paddy@3 | 110 return subscription, nil |
| paddy@2 | 111 } |
| paddy@2 | 112 |
| paddy@2 | 113 func StripeSubscriptionChange(orig Subscription, subscription stripe.Sub) SubscriptionChange { |
| paddy@2 | 114 var change SubscriptionChange |
| paddy@6 | 115 if subscription.ID != orig.StripeSubscription { |
| paddy@6 | 116 change.StripeSubscription = &subscription.ID |
| paddy@6 | 117 } |
| paddy@2 | 118 if subscription.Plan != nil && orig.Plan != subscription.Plan.ID { |
| paddy@2 | 119 change.Plan = &subscription.Plan.ID |
| paddy@2 | 120 } |
| paddy@2 | 121 if string(subscription.Status) != orig.Status { |
| paddy@2 | 122 status := string(subscription.Status) |
| paddy@2 | 123 change.Status = &status |
| paddy@2 | 124 } |
| paddy@2 | 125 if subscription.EndCancel != orig.Canceling { |
| paddy@2 | 126 change.Canceling = &subscription.EndCancel |
| paddy@2 | 127 } |
| paddy@3 | 128 if !time.Unix(subscription.TrialStart, 0).Equal(orig.TrialStart) && !(subscription.TrialStart == 0 && orig.TrialStart.IsZero()) { |
| paddy@2 | 129 trialStart := time.Unix(subscription.TrialStart, 0) |
| paddy@2 | 130 change.TrialStart = &trialStart |
| paddy@2 | 131 } |
| paddy@3 | 132 if !time.Unix(subscription.TrialEnd, 0).Equal(orig.TrialEnd) && !(subscription.TrialEnd == 0 && orig.TrialEnd.IsZero()) { |
| paddy@2 | 133 trialEnd := time.Unix(subscription.TrialEnd, 0) |
| paddy@2 | 134 change.TrialEnd = &trialEnd |
| paddy@2 | 135 } |
| paddy@3 | 136 if !time.Unix(subscription.PeriodStart, 0).Equal(orig.PeriodStart) && !(subscription.PeriodStart == 0 && orig.PeriodStart.IsZero()) { |
| paddy@2 | 137 periodStart := time.Unix(subscription.PeriodStart, 0) |
| paddy@2 | 138 change.PeriodStart = &periodStart |
| paddy@2 | 139 } |
| paddy@3 | 140 if !time.Unix(subscription.PeriodEnd, 0).Equal(orig.PeriodEnd) && !(subscription.PeriodEnd == 0 && orig.PeriodEnd.IsZero()) { |
| paddy@2 | 141 periodEnd := time.Unix(subscription.PeriodEnd, 0) |
| paddy@2 | 142 change.PeriodEnd = &periodEnd |
| paddy@2 | 143 } |
| paddy@3 | 144 if !time.Unix(subscription.Canceled, 0).Equal(orig.CanceledAt) && !(subscription.Canceled == 0 && orig.CanceledAt.IsZero()) { |
| paddy@2 | 145 canceledAt := time.Unix(subscription.Canceled, 0) |
| paddy@2 | 146 change.CanceledAt = &canceledAt |
| paddy@2 | 147 } |
| paddy@2 | 148 return change |
| paddy@2 | 149 } |