Create a listener that will create subscriptions.
We need a listener (as discussed in c4cfceb2f2fb) that will create a
Subscription whenever an auth.Profile is created. This is the beginning of that
effort. It hasn't been tested, and all the pieces aren't in place, but it's a
rough skeleton.
We have a Dockerfile that will correctly build a minimal container for the
listener.
We have a build-docker.sh script that will correctly build a binary that will be
used in the Dockerfile.
We have a ca-certificates.crt, which are pulled from Ubuntu, and are necessary
before we can safely consume SSL endpoints.
We created a small consumer script that listens for events off NSQ, and calls
the appropriate endpoint for our Subscriptions API. This is untested, and it
doesn't build at the moment, but that's awaiting changes in the
code.secondbit.org/auth.hg package.
Finally, we have a wrapper.sh file that will expose the Stripe secret key being
used from a Kubernetes secret file as an environment variable, instead.
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