// EnsureCustomerForGroup registers a customer for a group if it does not exist, // returns the existing one if created previously func EnsureCustomerForGroup(username string, groupName string, req *stripe.CustomerParams) (*stripe.Customer, error) { group, err := modelhelper.GetGroup(groupName) if err != nil { return nil, err } // if we already have the customer, return it. if group.Payment.Customer.ID != "" { return customer.Get(group.Payment.Customer.ID, nil) } req, err = populateCustomerParams(username, group.Slug, req) if err != nil { return nil, err } cus, err := customer.New(req) if err != nil { return nil, err } if err := modelhelper.UpdateGroupPartial( modelhelper.Selector{"_id": group.Id}, modelhelper.Selector{ "$set": modelhelper.Selector{ "payment.customer.id": cus.ID, }, }, ); err != nil { return nil, err } return cus, nil }
// GetCustomerForGroup get the registered customer info of a group if exists func GetCustomerForGroup(groupName string) (*stripe.Customer, error) { group, err := modelhelper.GetGroup(groupName) if err != nil { return nil, err } if group.Payment.Customer.ID == "" { return nil, ErrCustomerNotExists } return customer.Get(group.Payment.Customer.ID, nil) }
func TestSourceCardNew(t *testing.T) { customerParams := &stripe.CustomerParams{} cust, err := customer.New(customerParams) if err != nil { t.Error(err) } sourceParams := &stripe.CustomerSourceParams{ Customer: cust.ID, } sourceParams.SetSource(&stripe.CardParams{ Number: "4242424242424242", Month: "10", Year: "20", CVC: "1234", }) source, err := New(sourceParams) if err != nil { t.Error(err) } target := source.Card if target.LastFour != "4242" { t.Errorf("Unexpected last four %q for card number %v\n", target.LastFour, sourceParams.Source.Card.Number) } if target.CVCCheck != card.Pass { t.Errorf("CVC check %q does not match expected status\n", target.ZipCheck) } targetCust, err := customer.Get(cust.ID, nil) if err != nil { t.Error(err) } if targetCust.Sources.Count != 1 { t.Errorf("Unexpected number of sources %v\n", targetCust.Sources.Count) } customer.Del(cust.ID) }
func handleInvoiceStateChange(invoice *stripe.Invoice) error { cus, err := customer.Get(invoice.Customer.ID, nil) if err != nil { return err } if err := syncGroupWithCustomerID(invoice.Customer.ID); err != nil { return err } group, err := modelhelper.GetGroup(cus.Meta["groupName"]) // we might get events from other environments where we might not have the // group in this env. if err == mgo.ErrNotFound { return nil } if err != nil { return err } status := group.Payment.Subscription.Status // if sub is in cancelled state within 2 months send an event if stripe.SubStatus(status) == SubStatusCanceled { // if group has been created in last 2 months (1 month trial + 1 month free) totalTrialTime := time.Now().UTC().Add(-time.Hour * 24 * 60) if group.Id.Time().After(totalTrialTime) { eventName := "trial ended without payment" sendEventForCustomer(invoice.Customer.ID, eventName, nil) } } // send instance notification to group go realtimehelper.NotifyGroup( group.Slug, "payment_status_changed", map[string]string{ "oldStatus": string(group.Payment.Subscription.Status), "newStatus": string(status), }, ) eventName := fmt.Sprintf("subscription status %s", status) return sendEventForCustomer(invoice.Customer.ID, eventName, nil) }
func sendEventForCustomer(customerID string, eventName string, options map[string]interface{}) error { cus, err := customer.Get(customerID, nil) if err != nil { return err } if options == nil { options = make(map[string]interface{}) } for key, val := range cus.Meta { options[key] = val } admins, err := modelhelper.FetchAdminAccounts(cus.Meta["groupName"]) if err == mgo.ErrNotFound { return nil } if err != nil { return err } for _, admin := range admins { user, err := modelhelper.GetUser(admin.Profile.Nickname) if err != nil { return err } mail := &emailsender.Mail{ To: user.Email, Subject: eventName, Properties: &emailsender.Properties{ Username: user.Name, Options: options, }, } if err := mailSender(mail); err != nil { return err } } return nil }
func syncGroupWithCustomerID(cusID string) error { cus, err := customer.Get(cusID, nil) if err != nil { return err } group, err := modelhelper.GetGroup(cus.Meta["groupName"]) if err == mgo.ErrNotFound { return nil } if err != nil { return err } // here sub count might be 0, but should not be gt 1 if cus.Subs.Count > 1 { return errors.New("customer should only have one subscription") } subID := "" subStatus := SubStatusCanceled // if we dont have any sub, set it as canceled if cus.Subs.Count == 1 { subID = cus.Subs.Values[0].ID subStatus = cus.Subs.Values[0].Status } // if subID and subStatus are same, update not needed if group.Payment.Subscription.ID == subID && stripe.SubStatus(group.Payment.Subscription.Status) == subStatus { return nil } return modelhelper.UpdateGroupPartial( modelhelper.Selector{"_id": group.Id}, modelhelper.Selector{ "$set": modelhelper.Selector{ "payment.subscription.id": subID, "payment.subscription.status": string(subStatus), }, }, ) }
func TestCardNew(t *testing.T) { customerParams := &stripe.CustomerParams{} customerParams.SetSource(&stripe.CardParams{ Number: "378282246310005", Month: "06", Year: "20", }) cust, _ := customer.New(customerParams) cardParams := &stripe.CardParams{ Number: "4242424242424242", Month: "10", Year: "20", Customer: cust.ID, CVC: "1234", } target, err := New(cardParams) if err != nil { t.Error(err) } if target.LastFour != "4242" { t.Errorf("Unexpected last four %q for card number %v\n", target.LastFour, cardParams.Number) } if target.CVCCheck != Pass { t.Errorf("CVC check %q does not match expected status\n", target.ZipCheck) } targetCust, err := customer.Get(cust.ID, nil) if err != nil { t.Error(err) } if targetCust.Sources.Count != 2 { t.Errorf("Unexpected number of sources %v\n", targetCust.Sources.Count) } customer.Del(cust.ID) }
// CheckCustomerHasSource checks if the given customer has any kind of active // source. func CheckCustomerHasSource(cusID string) error { cus, err := customer.Get(cusID, nil) if err != nil { return err } count := 0 for _, source := range cus.Sources.Values { if !source.Deleted { count++ } } if count == 1 { return nil } return ErrCustomerSourceNotExists }
func TestCardNew(t *testing.T) { customerParams := &stripe.CustomerParams{} customerParams.SetSource(&stripe.CardParams{ Number: "378282246310005", Month: "06", Year: "20", }) cust, _ := customer.New(customerParams) cardParams := &stripe.CardParams{ Number: "4242424242424242", Month: "10", Year: "20", Customer: cust.ID, CVC: "1234", } target, err := New(cardParams) if err != nil { t.Error(err) } if target.LastFour != "4242" { t.Errorf("Unexpected last four %q for card number %v\n", target.LastFour, cardParams.Number) } if target.Meta == nil || len(target.Meta) > 0 { t.Errorf("Unexpected nil or non-empty metadata in card\n") } if target.Month != 10 { t.Errorf("Unexpected expiration month %d for card where we set %q\n", target.Month, cardParams.Month) } if target.Year != 2020 { t.Errorf("Unexpected expiration year %d for card where we set %q\n", target.Year, cardParams.Year) } if target.CVCCheck != Pass { t.Errorf("CVC check %q does not match expected status\n", target.ZipCheck) } targetCust, err := customer.Get(cust.ID, nil) if err != nil { t.Error(err) } if targetCust.Sources.Count != 2 { t.Errorf("Unexpected number of sources %v\n", targetCust.Sources.Count) } targetToken, err := token.New(&stripe.TokenParams{ Card: &stripe.CardParams{ Number: "4000056655665556", Month: "09", Year: "2021", CVC: "123", }, }) targetCard, err := New(&stripe.CardParams{ Customer: targetCust.ID, Token: targetToken.ID, }) if targetCard.LastFour != "5556" { t.Errorf("Unexpected last four %q for card number %v\n", targetCard.LastFour, cardParams.Number) } if targetCard.Month != 9 { t.Errorf("Unexpected expiration month %d for card where we set %q\n", targetCard.Month, targetToken.Card.Month) } if targetCard.Year != 2021 { t.Errorf("Unexpected expiration year %d for card where we set %q\n", targetCard.Year, targetToken.Card.Year) } customer.Del(cust.ID) }
func invoiceCreatedHandler(raw []byte) error { var invoice stripe.Invoice err := json.Unmarshal(raw, &invoice) if err != nil { return err } // we cant do anything to the closed invoices. if invoice.Closed { return nil } if invoice.Paid { return nil } cus, err := customer.Get(invoice.Customer.ID, nil) if err != nil { return err } group, err := modelhelper.GetGroup(cus.Meta["groupName"]) // we might get events from other environments where we might not have the // group in this env. if err == mgo.ErrNotFound { return nil } if err != nil { return err } // if customer id and subscription id is not set, we dont have the // appropriate data in our system, dont bother with the rest if group.Payment.Customer.ID == "" { return nil } if group.Payment.Subscription.ID == "" { return nil } info, err := GetInfoForGroup(group) if err == mgo.ErrNotFound { return nil } if err != nil { return err } if strings.HasPrefix(info.Subscription.Plan.ID, customPlanPrefix) { return (&models.PresenceDaily{}).ProcessByGroupName(group.Slug) } // if the amount that stripe will withdraw and what we want is same, so we // are done. Subtotal -> Total of all subscriptions, invoice items, and // prorations on the invoice before any discount is applied if invoice.Subtotal == int64(info.Due) { // clean up waiting deleted users return (&models.PresenceDaily{}).ProcessByGroupName(group.Slug) } // if this in the tests, just skip cancellation of the invoice, because // there is no way to create an invoice and have it open for a while. if invoice.ID != "in_00000000000000" { // first close the invoice _, err = stripeinvoice.Update(invoice.ID, &stripe.InvoiceParams{Closed: true}) if err != nil { return err } } return switchToNewSub(info) }