ducky/subscriptions
ducky/subscriptions/stripe.go
Fix go vet errors. We had a few logging statements that used placeholders but didn't provide any variables to fill them. We now specify the appropriate variables.
| 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 } |