Make subscriptions Kubernetes-ready.
Update our .hgignore file to include the docker-ready subscripionsd binary
(which differs from the local subscriptionsd binary only in that it's
statically-compiled for linux).
Add a replication controller for subscriptionsd that will spin up the
appropriate pods for us and make sure our Stripe and Subscriptionsd secret
volumes are attached, so the pods may configure themselves with that private
info.
Create a Stripe secret volume with a placeholder for the stripe secret key.
Create a Subscriptionsd secret volume with the DSN sent to the base64 encoding
of an empty string right now (which means subscriptionsd will store data in
memory, but whatever.)
Create a subscriptionsd service that will route traffic to the subscriptionsd
pods created by the replication controller.
Create a minimal Dockerfile to get the subscriptionsd binary running on
kubernetes.
Add a build-docker helper script that will compile a Docker-ready subscriptionsd
binary (by compiling it in a Docker container).
Copy a Ubuntu ca-certificates.crt file that we'll inject into our Dockerfile so
the Stripe SSL can be resolved. Busybox doesn't have any certificates, by
default.
Create a wrapper script that will be run in the Docker container to read the
secrets from our secret volumes and inject them as environment variables, which
is what subscriptionsd understands.
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"
16 customers customer.Client
17 subscriptions sub.Client
20 func NewStripe(apiKey string, backend stripe.Backend) Stripe {
23 customers: customer.Client{
27 subscriptions: sub.Client{
34 func CreateStripeCustomer(token, email string, userID uuid.ID, s Stripe) (*stripe.Customer, error) {
35 customerParams := &stripe.CustomerParams{
36 Desc: "Customer for user " + userID.String(),
39 customerParams.AddMeta("UserID", userID.String())
40 customerParams.SetSource(token)
41 c, err := s.customers.New(customerParams)
48 func CreateStripeSubscription(customer, plan string, userID uuid.ID, s Stripe) (*stripe.Sub, error) {
49 subParams := &stripe.SubParams{
53 subParams.AddMeta("UserID", userID.String())
55 resp, err := s.subscriptions.New(subParams)
62 func New(req SubscriptionRequest, s Stripe, store SubscriptionStore) (Subscription, error) {
63 subscription := SubscriptionFromRequest(req)
64 // create the subscription in our datastore
65 // this will fail if they already have a subscription, which prevents duplicate/orphaned Stripe customers being created
66 err := store.CreateSubscription(subscription)
68 return subscription, err
71 // create the customer in Stripe, storing the token for reuse
72 customer, err := CreateStripeCustomer(req.StripeToken, req.Email, req.UserID, s)
74 // TODO: delete subscription object
75 return subscription, err
78 // create the subscription in Stripe, storing the ID for tracking and associating purposes
79 stripeSub, err := CreateStripeSubscription(customer.ID, subscription.Plan, subscription.UserID, s)
81 // TODO: delete customer
82 // TODO: delete subscription object
83 return subscription, err
86 // update our subscription in the datastore with the latest information from Stripe
87 change := StripeSubscriptionChange(subscription, *stripeSub)
88 err = store.UpdateSubscription(subscription.UserID, change)
90 log.Printf("Error pairing Stripe subscription %s to user %s: %+v\nUser needs to have their subscription updated manually.", change.StripeSubscription, req.UserID, err)
91 return subscription, nil
93 subscription.ApplyChange(change)
94 return subscription, nil
97 func StripeSubscriptionChange(orig Subscription, subscription stripe.Sub) SubscriptionChange {
98 var change SubscriptionChange
99 if subscription.Plan != nil && orig.Plan != subscription.Plan.ID {
100 change.Plan = &subscription.Plan.ID
102 if string(subscription.Status) != orig.Status {
103 status := string(subscription.Status)
104 change.Status = &status
106 if subscription.EndCancel != orig.Canceling {
107 change.Canceling = &subscription.EndCancel
109 if !time.Unix(subscription.TrialStart, 0).Equal(orig.TrialStart) && !(subscription.TrialStart == 0 && orig.TrialStart.IsZero()) {
110 trialStart := time.Unix(subscription.TrialStart, 0)
111 change.TrialStart = &trialStart
113 if !time.Unix(subscription.TrialEnd, 0).Equal(orig.TrialEnd) && !(subscription.TrialEnd == 0 && orig.TrialEnd.IsZero()) {
114 trialEnd := time.Unix(subscription.TrialEnd, 0)
115 change.TrialEnd = &trialEnd
117 if !time.Unix(subscription.PeriodStart, 0).Equal(orig.PeriodStart) && !(subscription.PeriodStart == 0 && orig.PeriodStart.IsZero()) {
118 periodStart := time.Unix(subscription.PeriodStart, 0)
119 change.PeriodStart = &periodStart
121 if !time.Unix(subscription.PeriodEnd, 0).Equal(orig.PeriodEnd) && !(subscription.PeriodEnd == 0 && orig.PeriodEnd.IsZero()) {
122 periodEnd := time.Unix(subscription.PeriodEnd, 0)
123 change.PeriodEnd = &periodEnd
125 if !time.Unix(subscription.Canceled, 0).Equal(orig.CanceledAt) && !(subscription.Canceled == 0 && orig.CanceledAt.IsZero()) {
126 canceledAt := time.Unix(subscription.Canceled, 0)
127 change.CanceledAt = &canceledAt