Beispiel #1
0
// 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
}
Beispiel #2
0
// 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)
}
Beispiel #3
0
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)
}
Beispiel #4
0
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)
}
Beispiel #5
0
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
}
Beispiel #6
0
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),
			},
		},
	)
}
Beispiel #7
0
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)
}
Beispiel #8
0
// 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
}
Beispiel #9
0
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)
}
Beispiel #10
0
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)
}