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.
7 "code.secondbit.org/uuid.hg"
9 "github.com/stripe/stripe-go"
10 "github.com/stripe/stripe-go/customer"
11 "github.com/stripe/stripe-go/sub"
15 PendingPlan = "pending"
19 ErrNilCustomer = errors.New("nil customer passed")
20 ErrNilCustomerSubs = errors.New("customer with nil subscriptions list passed")
21 ErrWrongNumberOfCustomerSubs = errors.New("customer with wrong number of subscriptions passed")
22 ErrNilSubscription = errors.New("nil subscription passed")
27 customers customer.Client
28 subscriptions sub.Client
31 func NewStripe(apiKey string, backend stripe.Backend) Stripe {
34 customers: customer.Client{
38 subscriptions: sub.Client{
45 func CreateStripeCustomer(plan, email string, userID uuid.ID, s Stripe) (*stripe.Customer, error) {
46 customerParams := &stripe.CustomerParams{
47 Desc: "Customer for user " + userID.String(),
51 customerParams.AddMeta("UserID", userID.String())
52 c, err := s.customers.New(customerParams)
59 func UpdateStripeSubscription(customerID string, plan, token *string, s Stripe) (*stripe.Sub, error) {
60 params := &stripe.SubParams{}
67 subscription, err := s.subscriptions.Update(customerID, params)
71 return subscription, nil
74 // New should be called when a user's profile is created. At this point, we know nothing about the subscription
75 // they actually _want_. We just sign them up for the dedicated "pending" plan. This is to make their free trial begin
76 // immediately and not have to worry about automatically locking them out until they actually create a subscription.
77 // Basically, we want everyone to have a subscription at all times, but some users will have placeholders until they
78 // actually update their subscription with a desired plan and payment method.
79 func New(req SubscriptionChange, s Stripe, store SubscriptionStore) (Subscription, error) {
80 subscription := Subscription{}
81 subscription.ApplyChange(req)
82 // BUG(paddy): need to validate the change
84 // create the customer in Stripe, storing the token for reuse
85 customer, err := CreateStripeCustomer(PendingPlan, *req.Email, req.UserID, s)
87 return subscription, err
90 return subscription, ErrNilCustomer
92 if customer.Subs == nil {
93 return subscription, ErrNilCustomerSubs
95 if len(customer.Subs.Values) != 1 {
96 return subscription, ErrWrongNumberOfCustomerSubs
98 if customer.Subs.Values[0] == nil {
99 return subscription, ErrNilSubscription
102 change := StripeSubscriptionChange(subscription, *customer.Subs.Values[0])
103 subscription.ApplyChange(change)
105 err = store.CreateSubscription(subscription)
107 return subscription, err
110 return subscription, nil
113 func StripeSubscriptionChange(orig Subscription, subscription stripe.Sub) SubscriptionChange {
114 var change SubscriptionChange
115 if subscription.ID != orig.StripeSubscription {
116 change.StripeSubscription = &subscription.ID
118 if subscription.Plan != nil && orig.Plan != subscription.Plan.ID {
119 change.Plan = &subscription.Plan.ID
121 if string(subscription.Status) != orig.Status {
122 status := string(subscription.Status)
123 change.Status = &status
125 if subscription.EndCancel != orig.Canceling {
126 change.Canceling = &subscription.EndCancel
128 if !time.Unix(subscription.TrialStart, 0).Equal(orig.TrialStart) && !(subscription.TrialStart == 0 && orig.TrialStart.IsZero()) {
129 trialStart := time.Unix(subscription.TrialStart, 0)
130 change.TrialStart = &trialStart
132 if !time.Unix(subscription.TrialEnd, 0).Equal(orig.TrialEnd) && !(subscription.TrialEnd == 0 && orig.TrialEnd.IsZero()) {
133 trialEnd := time.Unix(subscription.TrialEnd, 0)
134 change.TrialEnd = &trialEnd
136 if !time.Unix(subscription.PeriodStart, 0).Equal(orig.PeriodStart) && !(subscription.PeriodStart == 0 && orig.PeriodStart.IsZero()) {
137 periodStart := time.Unix(subscription.PeriodStart, 0)
138 change.PeriodStart = &periodStart
140 if !time.Unix(subscription.PeriodEnd, 0).Equal(orig.PeriodEnd) && !(subscription.PeriodEnd == 0 && orig.PeriodEnd.IsZero()) {
141 periodEnd := time.Unix(subscription.PeriodEnd, 0)
142 change.PeriodEnd = &periodEnd
144 if !time.Unix(subscription.Canceled, 0).Equal(orig.CanceledAt) && !(subscription.Canceled == 0 && orig.CanceledAt.IsZero()) {
145 canceledAt := time.Unix(subscription.Canceled, 0)
146 change.CanceledAt = &canceledAt