ducky/subscriptions

Paddy 2015-06-22 Parent:61c4ce5850da Child:c4cfceb2f2fb

3:b240b6123548 Go to Latest

ducky/subscriptions/stripe.go

Export all subscriptionStore methods. We're not going to wrap all our subscriptionStore interactions in a Context type, so we need to expose all the functions so other packages can call them. Also, it's now SubscriptionStore. Also creates a SubscriptionRequest type that is used to create a new Subscription instance, along with a Validate method on it, to detect errors when creating a SubscriptionRequest. Update our CreateSubscription function to be New, and have it take a SubscriptionRequest, a Stripe instance, and a SubscriptionStore as arguments. It'll create the Subscription in the SubscriptionStore, create a customer in Stripe, and create the subscription in Stripe, then associate the Stripe subscription with the created Subscription. Also, fix our StripeSubscriptionChange function to correctly equate a missing/zero Unix timestamp from Stripe with a missing/zero time.Time.

History
paddy@2 1 package subscriptions
paddy@2 2
paddy@2 3 import (
paddy@3 4 "log"
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@2 14 type Stripe struct {
paddy@2 15 apiKey string
paddy@2 16 customers customer.Client
paddy@2 17 subscriptions sub.Client
paddy@2 18 }
paddy@2 19
paddy@2 20 func NewStripe(apiKey string, backend stripe.Backend) Stripe {
paddy@2 21 return Stripe{
paddy@2 22 apiKey: apiKey,
paddy@2 23 customers: customer.Client{
paddy@2 24 B: backend,
paddy@2 25 Key: apiKey,
paddy@2 26 },
paddy@2 27 subscriptions: sub.Client{
paddy@2 28 B: backend,
paddy@2 29 Key: apiKey,
paddy@2 30 },
paddy@2 31 }
paddy@2 32 }
paddy@2 33
paddy@2 34 func CreateStripeCustomer(token, email string, userID uuid.ID, s Stripe) (*stripe.Customer, error) {
paddy@2 35 customerParams := &stripe.CustomerParams{
paddy@2 36 Desc: "Customer for user " + userID.String(),
paddy@2 37 Email: email,
paddy@2 38 }
paddy@2 39 customerParams.AddMeta("UserID", userID.String())
paddy@2 40 customerParams.SetSource(token)
paddy@2 41 c, err := s.customers.New(customerParams)
paddy@2 42 if err != nil {
paddy@2 43 return nil, err
paddy@2 44 }
paddy@2 45 return c, nil
paddy@2 46 }
paddy@2 47
paddy@2 48 func CreateStripeSubscription(customer, plan string, userID uuid.ID, s Stripe) (*stripe.Sub, error) {
paddy@2 49 subParams := &stripe.SubParams{
paddy@2 50 Plan: plan,
paddy@2 51 Customer: customer,
paddy@2 52 }
paddy@2 53 subParams.AddMeta("UserID", userID.String())
paddy@2 54
paddy@2 55 resp, err := s.subscriptions.New(subParams)
paddy@2 56 if err != nil {
paddy@2 57 return nil, err
paddy@2 58 }
paddy@2 59 return resp, nil
paddy@2 60 }
paddy@2 61
paddy@3 62 func New(req SubscriptionRequest, s Stripe, store SubscriptionStore) (Subscription, error) {
paddy@3 63 subscription := SubscriptionFromRequest(req)
paddy@2 64 // create the subscription in our datastore
paddy@2 65 // this will fail if they already have a subscription, which prevents duplicate/orphaned Stripe customers being created
paddy@3 66 err := store.CreateSubscription(subscription)
paddy@2 67 if err != nil {
paddy@3 68 return subscription, err
paddy@2 69 }
paddy@2 70
paddy@2 71 // create the customer in Stripe, storing the token for reuse
paddy@3 72 customer, err := CreateStripeCustomer(req.StripeToken, req.Email, req.UserID, s)
paddy@2 73 if err != nil {
paddy@2 74 // TODO: delete subscription object
paddy@3 75 return subscription, err
paddy@2 76 }
paddy@2 77
paddy@2 78 // create the subscription in Stripe, storing the ID for tracking and associating purposes
paddy@2 79 stripeSub, err := CreateStripeSubscription(customer.ID, subscription.Plan, subscription.UserID, s)
paddy@2 80 if err != nil {
paddy@2 81 // TODO: delete customer
paddy@2 82 // TODO: delete subscription object
paddy@3 83 return subscription, err
paddy@2 84 }
paddy@2 85
paddy@2 86 // update our subscription in the datastore with the latest information from Stripe
paddy@2 87 change := StripeSubscriptionChange(subscription, *stripeSub)
paddy@3 88 err = store.UpdateSubscription(subscription.UserID, change)
paddy@2 89 if err != nil {
paddy@3 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)
paddy@3 91 return subscription, nil
paddy@2 92 }
paddy@3 93 subscription.ApplyChange(change)
paddy@3 94 return subscription, nil
paddy@2 95 }
paddy@2 96
paddy@2 97 func StripeSubscriptionChange(orig Subscription, subscription stripe.Sub) SubscriptionChange {
paddy@2 98 var change SubscriptionChange
paddy@2 99 if subscription.Plan != nil && orig.Plan != subscription.Plan.ID {
paddy@2 100 change.Plan = &subscription.Plan.ID
paddy@2 101 }
paddy@2 102 if string(subscription.Status) != orig.Status {
paddy@2 103 status := string(subscription.Status)
paddy@2 104 change.Status = &status
paddy@2 105 }
paddy@2 106 if subscription.EndCancel != orig.Canceling {
paddy@2 107 change.Canceling = &subscription.EndCancel
paddy@2 108 }
paddy@3 109 if !time.Unix(subscription.TrialStart, 0).Equal(orig.TrialStart) && !(subscription.TrialStart == 0 && orig.TrialStart.IsZero()) {
paddy@2 110 trialStart := time.Unix(subscription.TrialStart, 0)
paddy@2 111 change.TrialStart = &trialStart
paddy@2 112 }
paddy@3 113 if !time.Unix(subscription.TrialEnd, 0).Equal(orig.TrialEnd) && !(subscription.TrialEnd == 0 && orig.TrialEnd.IsZero()) {
paddy@2 114 trialEnd := time.Unix(subscription.TrialEnd, 0)
paddy@2 115 change.TrialEnd = &trialEnd
paddy@2 116 }
paddy@3 117 if !time.Unix(subscription.PeriodStart, 0).Equal(orig.PeriodStart) && !(subscription.PeriodStart == 0 && orig.PeriodStart.IsZero()) {
paddy@2 118 periodStart := time.Unix(subscription.PeriodStart, 0)
paddy@2 119 change.PeriodStart = &periodStart
paddy@2 120 }
paddy@3 121 if !time.Unix(subscription.PeriodEnd, 0).Equal(orig.PeriodEnd) && !(subscription.PeriodEnd == 0 && orig.PeriodEnd.IsZero()) {
paddy@2 122 periodEnd := time.Unix(subscription.PeriodEnd, 0)
paddy@2 123 change.PeriodEnd = &periodEnd
paddy@2 124 }
paddy@3 125 if !time.Unix(subscription.Canceled, 0).Equal(orig.CanceledAt) && !(subscription.Canceled == 0 && orig.CanceledAt.IsZero()) {
paddy@2 126 canceledAt := time.Unix(subscription.Canceled, 0)
paddy@2 127 change.CanceledAt = &canceledAt
paddy@2 128 }
paddy@2 129 return change
paddy@2 130 }