//GetAll retrieves the list of all users in the datastore
//the data is pulled from memcache or the datastore
//the data is returned as a json to populate select menus in the gui
func GetAll(w http.ResponseWriter, r *http.Request) {
	//check if list of users is in memcache
	result := make([]userList, 0, 5)
	c := appengine.NewContext(r)
	_, err := memcache.Gob.Get(c, listOfUsersKey, &result)
	if err == nil {
		output.Success("userList-cached", result, w)
		return
	}

	//list of cards not found in memcache
	//get list from datastore
	//only need to get username and entity key to cut down on datastore usage
	//save the list to memcache for faster retrieval next time
	if err == memcache.ErrCacheMiss {
		q := datastore.NewQuery(datastoreKind).Order("Username").Project("Username")
		users := make([]User, 0, 5)
		keys, err := q.GetAll(c, &users)
		if err != nil {
			output.Error(err, "Error retrieving list of users from datastore.", w, r)
			return
		}

		//build result
		//format data to show just datastore id and username
		//creates a map of structs
		idsAndNames := make([]userList, 0, 5)
		for i, r := range users {
			x := userList{
				Username: r.Username,
				Id:       keys[i].IntID(),
			}

			idsAndNames = append(idsAndNames, x)
		}

		//save the list of users to memcache
		//ignore errors since we still retrieved the data
		memcacheutils.Save(c, listOfUsersKey, idsAndNames)

		//return data to clinet
		output.Success("userList", idsAndNames, w)
		return

	} else if err != nil {
		output.Error(err, "Unknown error retrieving list of users.", w, r)
		return
	}

	return
}
//GetAll retrieves the list of all cards in the datastore (datastore id and customer name only)
//the data is pulled from memcache or the datastore
//the data is returned as json to populate the datalist in the html ui
func GetAll(w http.ResponseWriter, r *http.Request) {
	//check if list of cards is in memcache
	c := appengine.NewContext(r)
	result := make([]CardList, 0, 50)
	_, err := memcache.Gob.Get(c, listOfCardsKey, &result)
	if err == nil {
		output.Success("cardlist-cached", result, w)
		return
	}

	//list of cards not found in memcache
	//get list from datastore
	//only need to get entity keys and customer names: cuts down on datastore usage
	//save the list to memcache for faster retrieval next time
	if err == memcache.ErrCacheMiss {
		q := datastore.NewQuery(datastoreKind).Order("CustomerName").Project("CustomerName")
		cards := make([]CustomerDatastore, 0, 50)
		keys, err := q.GetAll(c, &cards)
		if err != nil {
			output.Error(err, "Error retrieving list of cards from datastore.", w, r)
			return
		}

		//build result
		//format data to show just datastore id and customer name
		//creates a map of structs
		idAndNames := make([]CardList, 0, 50)
		for i, r := range cards {
			x := CardList{r.CustomerName, keys[i].IntID()}
			idAndNames = append(idAndNames, x)
		}

		//save list of cards to memcache
		//ignore errors since we already got the data
		memcacheutils.Save(c, listOfCardsKey, idAndNames)

		//return data to client
		output.Success("cardList-datastore", idAndNames, w)
		return

	} else if err != nil {
		output.Error(err, "Unknown error retrieving list of cards.", w, r)
		return
	}

	return
}
//ChangePwd is used to change a user's password
func ChangePwd(w http.ResponseWriter, r *http.Request) {
	//gather inputs
	userId := r.FormValue("userId")
	userIdInt, _ := strconv.ParseInt(userId, 10, 64)
	password1 := r.FormValue("pass1")
	password2 := r.FormValue("pass2")

	//make sure passwords match
	if doStringsMatch(password1, password2) == false {
		output.Error(ErrPasswordsDoNotMatch, "The passwords you provided to not match.", w, r)
		return
	}

	//make sure password is long enough
	if len(password1) < minPwdLength {
		output.Error(ErrPasswordTooShort, "The password you provided is too short. It must be at least "+strconv.FormatInt(minPwdLength, 10)+" characters.", w, r)
		return
	}

	//hash the password
	hashedPwd := pwds.Create(password1)

	//get user data
	c := appengine.NewContext(r)
	userData, err := Find(c, userIdInt)
	if err != nil {
		output.Error(err, "Error while retreiving user data to update user's password.", w, r)
		return
	}

	//set new password
	userData.Password = hashedPwd

	//clear memcache for this userID & username
	err = memcacheutils.Delete(c, userId)
	err1 := memcacheutils.Delete(c, userData.Username)
	if err != nil {
		output.Error(err, "Error clearing cache for user id.", w, r)
		return
	} else if err1 != nil {
		output.Error(err1, "Error clearing cache for username.", w, r)
		return
	}

	//generate full datastore key for user
	fullKey := getUserKeyFromId(c, userIdInt)

	//save user
	_, err = saveUser(c, fullKey, userData)
	if err != nil {
		output.Error(err, "Error saving user to database after password change.", w, r)
		return
	}

	//done
	output.Success("userChangePassword", nil, w)
	return
}
//Remove removes a card from the datastore, memcache, and stripe
func Remove(w http.ResponseWriter, r *http.Request) {
	//get form values
	datastoreId := r.FormValue("customerId")
	datastoreIdInt, _ := strconv.ParseInt(datastoreId, 10, 64)

	//make sure an id was given
	if len(datastoreId) == 0 {
		output.Error(ErrMissingInput, "A customer's datastore ID must be given but was missing. This value is different from your \"Customer ID\" and should have been submitted automatically.", w, r)
		return
	}

	//init stripe
	c := appengine.NewContext(r)
	sc := createAppengineStripeClient(c)

	//delete customer on stripe
	custData, err := findByDatastoreId(c, datastoreIdInt)
	if err != nil {
		output.Error(err, "An error occured while trying to look up customer's Stripe information.", w, r)
	}
	stripeCustId := custData.StripeCustomerToken
	sc.Customers.Del(stripeCustId)

	//delete custome from datastore
	completeKey := getCustomerKeyFromId(c, datastoreIdInt)
	err = datastore.Delete(c, completeKey)
	if err != nil {
		output.Error(err, "There was an error while trying to delete this customer. Please try again.", w, r)
		return
	}

	//delete customer from memcache
	//delete list of cards in memcache since this list is now stale
	//all memcache.Delete operations are listed first so error handling doesn't return if one fails...each call does not depend on another so this is safe
	//obviously, if the card is not in the cache it cannot be removed
	err1 := memcache.Delete(c, datastoreId)
	err2 := memcache.Delete(c, custData.CustomerId)
	err3 := memcache.Delete(c, listOfCardsKey)
	if err1 != nil && err1 != memcache.ErrCacheMiss {
		output.Error(err1, "There was an error flushing this card's data from the cache (by datastore id). Please contact an administrator and have them flush the cache manually.", w, r)
		return
	}
	if err2 != nil && err2 != memcache.ErrCacheMiss {
		output.Error(err2, "There was an error flushing this card's data from the cache (by customer id). Please contact an administrator and have them flush the cache manually.", w, r)
		return
	}
	if err3 != nil && err3 != memcache.ErrCacheMiss {
		output.Error(err3, "There was an error flushing the cached list of cards.", w, r)
		return
	}

	//customer removed
	//return to client
	output.Success("removeCustomer", nil, w)
	return
}
//GetCompanyInfo is used when viewing the data in the gui or on a receipt
func GetCompanyInfo(w http.ResponseWriter, r *http.Request) {
	//get info
	info, err := getCompanyInfo(r)
	if err != nil {
		output.Error(err, "", w, r)
		return
	}

	output.Success("dataFound", info, w)
	return

}
//SaveCompanyInfo saves new or updates existing company info in the datastore
func SaveCompanyInfo(w http.ResponseWriter, r *http.Request) {
	//get form values
	name := strings.TrimSpace(r.FormValue("name"))
	street := strings.TrimSpace(r.FormValue("street"))
	suite := strings.TrimSpace(r.FormValue("suite"))
	city := strings.TrimSpace(r.FormValue("city"))
	state := strings.TrimSpace(r.FormValue("state"))
	postal := strings.TrimSpace(r.FormValue("postal"))
	country := strings.TrimSpace(r.FormValue("country"))
	phone := strings.TrimSpace(r.FormValue("phone"))

	//look up data for this company
	//may return a blank struct if this data does not exist yet
	data, err := getCompanyInfo(r)
	if err != nil && err != ErrCompanyDataDoesNotExist {
		output.Error(err, "", w, r)
		return
	}

	//context
	c := appengine.NewContext(r)

	//generate entity key
	//keyname is hard coded so only one entity exists
	key := datastore.NewKey(c, datastoreKind, datastoreKey, 0, nil)

	//build entity to save
	//or update existing entity
	data.CompanyName = name
	data.Street = street
	data.Suite = suite
	data.City = city
	data.State = strings.ToUpper(state)
	data.PostalCode = postal
	data.Country = strings.ToUpper(country)
	data.PhoneNum = phone

	//save company info
	_, err = datastore.Put(c, key, &data)
	if err != nil {
		output.Error(err, "", w, r)
		return
	}

	//save company into to memcache
	//ignoring errors since we can always get data from the datastore
	memcacheutils.Save(c, memcacheKeyName, data)

	//done
	output.Success("dataSaved", data, w)
	return
}
//GetOne retrieves the full data for one user
//this is used to fill in the edit user modal in the gui
func GetOne(w http.ResponseWriter, r *http.Request) {
	//get user id from form value
	userId := r.FormValue("userId")
	userIdInt, _ := strconv.ParseInt(userId, 10, 64)

	//get user data
	c := appengine.NewContext(r)
	data, err := Find(c, userIdInt)
	if err != nil {
		output.Error(err, "Cannot look up user data.", w, r)
		return
	}

	//return user data
	output.Success("findUser", data, w)
	return
}
//GetOne retrieves the full data for one card from the datastore
//this is used to fill in the "charge card" panel with identifying info on the card so the user car verify they are charging the correct card
func GetOne(w http.ResponseWriter, r *http.Request) {
	//get form value
	datastoreId := r.FormValue("customerId")
	datstoreIdInt, _ := strconv.ParseInt(datastoreId, 10, 64)

	//get customer card data
	c := appengine.NewContext(r)
	data, err := findByDatastoreId(c, datstoreIdInt)
	if err != nil {
		output.Error(err, "Could not find this customer's data.", w, r)
		return
	}

	//return data to client
	output.Success("cardFound", data, w)
	return
}
//REFUND A CHARGE
func Refund(w http.ResponseWriter, r *http.Request) {
	//get form values
	chargeId := r.FormValue("chargeId")
	amount := r.FormValue("amount")
	reason := r.FormValue("reason")

	//make sure inputs were given
	if len(chargeId) == 0 {
		output.Error(ErrMissingInput, "A charge ID was not provided. This is a serious error. Please contact an administrator.", w, r)
		return
	}
	if len(amount) == 0 {
		output.Error(ErrMissingInput, "No amount was given to refund.", w, r)
		return
	}

	//convert refund amount to cents
	//stripe requires cents
	amountCents, err := getAmountAsIntCents(amount)
	if err != nil {
		output.Error(err, "An error occured while converting the amount to charge into cents. Please try again or contact an administrator.", w, r)
		return
	}

	//get username of logged in user
	//for tracking who processed this refund
	session := sessionutils.Get(r)
	username := session.Values["username"].(string)

	//build refund
	params := &stripe.RefundParams{
		Charge: chargeId,
		Amount: amountCents,
	}

	//add metadata to refund
	//same field name as when creating a charge
	params.AddMeta("charged_by", username)

	//get reason code for refund
	//these are defined by stripe
	if reason == "duplicate" {
		params.Reason = refund.RefundDuplicate
	} else if reason == "requested_by_customer" {
		params.Reason = refund.RefundRequestedByCustomer
	}

	//init stripe
	c := appengine.NewContext(r)
	sc := createAppengineStripeClient(c)

	//create refund with stripe
	_, err = sc.Refunds.New(params)
	if err != nil {
		stripeErr := err.(*stripe.Error)
		stripeErrMsg := stripeErr.Msg
		output.Error(ErrStripe, stripeErrMsg, w, r)
		return
	}

	//done
	output.Success("refund-done", nil, w)
	return
}
//UpdatePermissions is used to save changes to a user's permissions (access rights)
//super-admin "administrator" account cannot be edited...this user always has full permissions
//you can not edit your own permissions so you don't lock yourself out of the app
func UpdatePermissions(w http.ResponseWriter, r *http.Request) {
	//gather form values
	userId := r.FormValue("userId")
	userIdInt, _ := strconv.ParseInt(userId, 10, 64)
	addCards, _ := strconv.ParseBool(r.FormValue("addCards"))
	removeCards, _ := strconv.ParseBool(r.FormValue("removeCards"))
	chargeCards, _ := strconv.ParseBool(r.FormValue("chargeCards"))
	viewReports, _ := strconv.ParseBool(r.FormValue("reports"))
	isAdmin, _ := strconv.ParseBool(r.FormValue("admin"))
	isActive, _ := strconv.ParseBool(r.FormValue("active"))

	//check if the logged in user is an admin
	//user updating another user's permission must be an admin
	//failsafe/second check since non-admins would not see the settings panel anyway
	session := sessionutils.Get(r)
	if session.IsNew {
		output.Error(ErrSessionMismatch, "An error occured. Please log out and log back in.", w, r)
		return
	}

	//get user data to update
	c := appengine.NewContext(r)
	userData, err := Find(c, userIdInt)
	if err != nil {
		output.Error(err, "We could not retrieve this user's information. This user could not be updates.", w, r)
		return
	}

	//check if the logged in user is trying to update their own permissions
	//you cannot edit your own permissions no matter what
	if session.Values["username"].(string) == userData.Username {
		output.Error(ErrCannotUpdateSelf, "You cannot edit your own permissions. Please contact another administrator.", w, r)
		return
	}

	//check if user is editing the super admin user
	if userData.Username == adminUsername {
		output.Error(ErrCannotUpdateSuperAdmin, "You cannot update the 'administrator' user. The account is locked.", w, r)
		return
	}

	//update the user
	userData.AddCards = addCards
	userData.RemoveCards = removeCards
	userData.ChargeCards = chargeCards
	userData.ViewReports = viewReports
	userData.Administrator = isAdmin
	userData.Active = isActive

	//clear memcache
	err = memcacheutils.Delete(c, userId)
	err1 := memcacheutils.Delete(c, userData.Username)
	if err != nil {
		output.Error(err, "Error clearing cache for user id.", w, r)
		return
	} else if err1 != nil {
		output.Error(err1, "Error clearing cache for username.", w, r)
		return
	}

	//generate complete key for user
	completeKey := getUserKeyFromId(c, userIdInt)

	//resave user
	//saves to datastore and memcache
	//save user
	_, err = saveUser(c, completeKey, userData)
	if err != nil {
		output.Error(err, "Error saving user to database after updating permission.", w, r)
		return
	}

	//done
	output.Success("userUpdatePermissins", nil, w)
	return
}
//Add saves a new user to the app
func Add(w http.ResponseWriter, r *http.Request) {
	//get form values
	username := r.FormValue("username")
	password1 := r.FormValue("password1")
	password2 := r.FormValue("password2")
	addCards, _ := strconv.ParseBool(r.FormValue("addCards"))
	removeCards, _ := strconv.ParseBool(r.FormValue("removeCards"))
	chargeCards, _ := strconv.ParseBool(r.FormValue("chargeCards"))
	viewReports, _ := strconv.ParseBool(r.FormValue("reports"))
	isAdmin, _ := strconv.ParseBool(r.FormValue("admin"))
	isActive, _ := strconv.ParseBool(r.FormValue("active"))

	//check if this user already exists
	c := appengine.NewContext(r)
	_, _, err := exists(c, username)
	if err == nil {
		//user already exists
		//notify client
		output.Error(ErrUserAlreadyExists, "This username already exists. Please choose a different username.", w, r)
		return
	}

	//make sure passwords match
	if doStringsMatch(password1, password2) == false {
		output.Error(ErrPasswordsDoNotMatch, "The passwords you provided to not match.", w, r)
		return
	}

	//make sure password is long enough
	if len(password1) < minPwdLength {
		output.Error(ErrPasswordTooShort, "The password you provided is too short. It must be at least "+strconv.FormatInt(minPwdLength, 10)+" characters.", w, r)
		return
	}

	//hash the password
	hashedPwd := pwds.Create(password1)

	//create the user
	u := User{
		Username:      username,
		Password:      hashedPwd,
		AddCards:      addCards,
		RemoveCards:   removeCards,
		ChargeCards:   chargeCards,
		ViewReports:   viewReports,
		Administrator: isAdmin,
		Active:        isActive,
		Created:       timestamps.ISO8601(),
	}

	//save to datastore
	incompleteKey := createNewUserKey(c)
	_, err = saveUser(c, incompleteKey, u)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}

	//clear list of users saved in memcache since a new user was added
	memcacheutils.Delete(c, listOfUsersKey)

	//respond to client with success message
	output.Success("addNewUser", nil, w)
	return
}
//Charge charges a credit card
func Charge(w http.ResponseWriter, r *http.Request) {
	//get form values
	datastoreId := r.FormValue("datastoreId")
	customerName := r.FormValue("customerName")
	amount := r.FormValue("amount")
	invoice := r.FormValue("invoice")
	poNum := r.FormValue("po")

	//validation
	if len(datastoreId) == 0 {
		output.Error(ErrMissingInput, "A customer ID should have been submitted automatically but was not. Please contact an administrator.", w, r)
		return
	}
	if len(amount) == 0 {
		output.Error(ErrMissingInput, "No amount was provided. You cannot charge a card nothing!", w, r)
		return
	}

	//get amount as cents
	amountCents, err := getAmountAsIntCents(amount)
	if err != nil {
		output.Error(err, "An error occured while converting the amount to charge into cents. Please try again or contact an administrator.", w, r)
		return
	}

	//check if amount is greater than the minimum charge
	//min charge may be greater than 0 because of transactions costs
	//for example, stripe takes 30 cents...it does not make sense to charge a card for < 30 cents
	if amountCents < minCharge {
		output.Error(ErrChargeAmountTooLow, "You must charge at least "+strconv.FormatInt(minCharge, 10)+" cents.", w, r)
		return
	}

	//create context
	//need to adjust deadline in case stripe takes longer than 5 seconds
	//default timeout for a urlfetch is 5 seconds
	//sometimes charging a card through stripe api takes longer
	//calls seems to take roughly 2 seconds normally with a few near 5 seconds (old deadline)
	//the call might still complete via stripe but appengine will return to the gui that it failed
	//10 secodns is a bit over generous but covers even really strange senarios
	c := appengine.NewContext(r)
	c, _ = context.WithTimeout(c, 10*time.Second)

	//look up stripe customer id from datastore
	datastoreIdInt, _ := strconv.ParseInt(datastoreId, 10, 64)
	custData, err := findByDatastoreId(c, datastoreIdInt)
	if err != nil {
		output.Error(err, "An error occured while looking up the customer's Stripe information.", w, r)
		return
	}

	//make sure customer name matches
	//just another catch in case of strange errors and mismatched data
	if customerName != custData.CustomerName {
		output.Error(err, "The customer name did not match the data for the customer ID. Please log out and try again.", w, r)
		return
	}

	//get username of logged in user
	//used for tracking who processed a charge
	//for audits and reports
	session := sessionutils.Get(r)
	username := session.Values["username"].(string)

	//init stripe
	sc := createAppengineStripeClient(c)

	//build charge object
	chargeParams := &stripe.ChargeParams{
		Customer:  custData.StripeCustomerToken,
		Amount:    amountCents,
		Currency:  currency,
		Desc:      "Charge for invoice: " + invoice + ", purchase order: " + poNum + ".",
		Statement: formatStatementDescriptor(),
	}

	//add metadata to charge
	//used for reports and receipts
	chargeParams.AddMeta("customer_name", customerName)
	chargeParams.AddMeta("datastore_id", datastoreId)
	chargeParams.AddMeta("customer_id", custData.CustomerId)
	chargeParams.AddMeta("invoice_num", invoice)
	chargeParams.AddMeta("po_num", poNum)
	chargeParams.AddMeta("charged_by", username)

	//process the charge
	chg, err := sc.Charges.New(chargeParams)

	//handle errors
	//*url.Error can be thrown if urlfetch reaches timeout (request took too long to complete)
	//*stripe.Error is a error with the stripe api and should return a human readable error message
	if err != nil {
		errorMsg := ""

		switch err.(type) {
		default:
			errorMsg = "There was an error processing this charge. Please check the Report to see if this charge was successful."
			break
		case *url.Error:
			errorMsg = "Charging this card timed out. The charge may have succeeded anyway. Please check the Report to see if this charge was successful."
			break
		case *stripe.Error:
			stripeErr := err.(*stripe.Error)
			errorMsg = stripeErr.Msg
		}

		output.Error(ErrStripe, errorMsg, w, r)
		return
	}

	//charge successful
	//save charge to memcache
	//less data to get from stripe if receipt is needed
	//errors are ignores since if we can't save this data to memcache we can always get it from the datastore/stripe
	memcacheutils.Save(c, chg.ID, chg)

	//save count of card types
	//used for negotiating rates with Stripe and just extra info
	saveChargeDetails(c, chg)

	//build struct to output a success message to the client
	out := chargeSuccessful{
		CustomerName:   customerName,
		Cardholder:     custData.Cardholder,
		CardExpiration: custData.CardExpiration,
		CardLast4:      custData.CardLast4,
		Amount:         amount,
		Invoice:        invoice,
		Po:             poNum,
		Datetime:       timestamps.ISO8601(),
		ChargeId:       chg.ID,
	}
	output.Success("cardCharged", out, w)
	return
}
//Add adds a new card the the app engine datastore
//this is done by validating the provided inputs, sending the card token to stripe, and saving the data to the datastore
//the card token was generaged client side by the stipe-js
//  this is done so the card number and security code is never sent to the server
//  the server has no way of "touching" the card number for security
//when the card token is sent to stripe, stripe generates a customer token which we store and use to process payments
func Add(w http.ResponseWriter, r *http.Request) {
	//get form values
	customerId := r.FormValue("customerId")     //a unique key, not the datastore id or stripe customer id
	customerName := r.FormValue("customerName") //user provided, could be company name/client name/may be same as cardholder
	cardholder := r.FormValue("cardholder")     //name on card as it appears
	cardToken := r.FormValue("cardToken")       //from stripejs
	cardExp := r.FormValue("cardExp")           //from stripejs, not from html input
	cardLast4 := r.FormValue("cardLast4")       //from stripejs, not from html input

	//make sure all form values were given
	if len(customerName) == 0 {
		output.Error(ErrMissingCustomerName, "You did not provide the customer's name.", w, r)
		return
	}
	if len(cardholder) == 0 {
		output.Error(ErrMissingCustomerName, "You did not provide the cardholer's name.", w, r)
		return
	}
	if len(cardToken) == 0 {
		output.Error(ErrMissingCardToken, "A serious error occured; the card token is missing. Please refresh the page and try again.", w, r)
		return
	}
	if len(cardExp) == 0 {
		output.Error(ErrMissingExpiration, "The card's expiration date is missing from Stripe. Please refresh the page and try again.", w, r)
		return
	}
	if len(cardLast4) == 0 {
		output.Error(ErrMissingLast4, "The card's last four digits are missing from Stripe. Please refresh the page and try again.", w, r)
		return
	}

	//init context
	c := appengine.NewContext(r)

	//if customerId was given, make sure it is unique
	//this id should be unique in the user's company's crm
	//the customerId is used to autofill the charge card panel when performing the api-like semi-automated charges
	if len(customerId) != 0 {
		_, err := FindByCustId(c, customerId)
		if err == nil {
			//customer already exists
			output.Error(ErrCustIdAlreadyExists, "This customer ID is already in use. Please double check your records or remove the customer with this customer ID first.", w, r)
			return
		} else if err != ErrCustomerNotFound {
			output.Error(err, "An error occured while verifying this customer ID does not already exist. Please try again or leave the customer ID blank.", w, r)
			return
		}
	}

	//init stripe
	sc := createAppengineStripeClient(c)

	//create the customer on stripe
	//assigns the card via the cardToken to this customer
	//this card is used when making charges to this customer
	custParams := &stripe.CustomerParams{Desc: customerName}
	custParams.SetSource(cardToken)
	cust, err := sc.Customers.New(custParams)
	if err != nil {
		stripeErr := err.(*stripe.Error)
		stripeErrMsg := stripeErr.Msg
		output.Error(ErrStripe, stripeErrMsg, w, r)
		return
	}

	//get username of logged in user
	//used for tracking who added a card, just for diagnostics
	session := sessionutils.Get(r)
	username := session.Values["username"].(string)

	//save customer & card data to datastore
	newCustKey := createNewCustomerKey(c)
	newCustomer := CustomerDatastore{
		CustomerId:          customerId,
		CustomerName:        customerName,
		Cardholder:          cardholder,
		CardExpiration:      cardExp,
		CardLast4:           cardLast4,
		StripeCustomerToken: cust.ID,
		DatetimeCreated:     timestamps.ISO8601(),
		AddedByUser:         username,
	}
	_, err = save(c, newCustKey, newCustomer)
	if err != nil {
		output.Error(err, "There was an error while saving this customer. Please try again.", w, r)
		return
	}

	//customer saved
	//return to client
	output.Success("createCustomer", nil, w)

	//resave list of cards in memcache
	//since a card was added, memcache is stale
	//clients will retreive new list when refreshing page/app
	memcacheutils.Delete(c, listOfCardsKey)
	return
}