ducky/subscriptions

Paddy 2015-07-18 Parent:c4cfceb2f2fb Child:1ff031bebf9e

10:2c8250237566 Go to Latest

ducky/subscriptions/stripe.go

Update subscription_creator to use the new strategy. When creating subscriptions through the client, detect when the returned error is saying the account already has a subscription, or the subscription already exists in stripe. Add an UpdateSubscription function that will update a subscription through the API. Update our subscription_creator listener to listen for profile creation and login verification messages. This mainly involved fixing the constants (the system, model, and topic) that the listener for profile creation was listening for. It also meant adding a new updateMessageHandler that listens for login verification, tries to create a subscription that has the user ID and email of the login that was verified, and if a subscription already exists, updates it instead to use the email address that was just verified. This will ensure that users get their receipts automatically emailed to them by Stripe.

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@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 }