//Root is used to show the login page of the app //when a user browses to the page (usually just the domain minus any path), the user is checked for a session //if a session exists, the app attempts to auto-login the user //otherwise a user is shown the log in prompt //this also handles the "first run" of the app in which no users exist yet...it forces creation of the "super admin" func Root(w http.ResponseWriter, r *http.Request) { //check that session store was initialized correctly if err := sessionutils.CheckSession(); err != nil { notificationPage(w, "panel-danger", sessionInitError, err, "btn-default", "/", "Go Back") return } //check that stripe private key and statement desecriptor were read correctly if err := card.CheckStripe(); err != nil { notificationPage(w, "panel-danger", sessionInitError, err, "btn-default", "/", "Go Back") return } //check if the admin user exists //redirect user to create admin if it does not exist err := users.DoesAdminExist(r) if err == users.ErrAdminDoesNotExist { http.Redirect(w, r, "/setup/", http.StatusFound) return } else if err != nil { notificationPage(w, "panel-danger", adminInitError, err, "btn-default", "/", "Go Back") return } //check if user is already signed in //if user is already logged in, redirect to /main/ page session := sessionutils.Get(r) if session.IsNew == false { uId := session.Values["user_id"].(int64) c := appengine.NewContext(r) u, err := users.Find(c, uId) if err != nil { sessionutils.Destroy(w, r) notificationPage(w, "panel-danger", "Autologin Error", "There was an issue looking up your user account. Please go back and try logging in.", "btn-default", "/", "Go Back") return } //user data was found //check if user is allowed access if users.AllowedAccess(u) == false { sessionutils.Destroy(w, r) notificationPage(w, "panel-danger", "Autologin Error", "You are not allowed access. Please contact an administrator.", "btn-default", "/", "Go Back") } //user account is found an allowed access //redirect user http.Redirect(w, r, "/main/", http.StatusFound) return } //load the login page templates.Load(w, "root", nil) return }
//CreateAdminShow loads the page used to create the initial admin user //this is done only upon the app running for the first time (per project on app engine since nothing exists in this project's datastore yet) func CreateAdminShow(w http.ResponseWriter, r *http.Request) { //check if the admin user already exists //no need to show this page if it does exist err := users.DoesAdminExist(r) if err == nil { http.Redirect(w, r, "/", http.StatusFound) return } templates.Load(w, "create-admin", nil) return }
//Diagnostics shows a bunch of app engine's information for the app/project //useful for figuring out which version of an app is serving func Diagnostics(w http.ResponseWriter, r *http.Request) { c := appengine.NewContext(r) out := map[string]interface{}{ "App ID": appengine.AppID(c), "Instance ID": appengine.InstanceID(), "Default Version Hostname": appengine.DefaultVersionHostname(c), "Version ID": appengine.VersionID(c), "Datacenter": appengine.Datacenter(c), "Module Name": appengine.ModuleName(c), "Server Software": appengine.ServerSoftware(), } templates.Load(w, "diagnostics", out) return }
//notificationPage is used to show html page for errors //same as pages.notificationPage but have to have separate function b/c of dependency circle func notificationPage(w http.ResponseWriter, panelType, title string, err interface{}, btnType, btnPath, btnText string) { templates.Load(w, "notifications", templates.NotificationPage{panelType, title, err, btnType, btnPath, btnText}) return }
//Show builds an html page that display a receipt //this is a very boring, plain test, monospaced font page designed for easy printing and reading //the receipt is generated from the charge id //the data for the charge may be in memcache or will have to be retrieved from stripe func Show(w http.ResponseWriter, r *http.Request) { //get charge id from form value chargeId := r.FormValue("chg_id") //try looking up charge data in memcache var chg *stripe.Charge c := appengine.NewContext(r) _, err := memcache.Gob.Get(c, chargeId, &chg) //charge not found in memcache //look up charge data from stripe if err == memcache.ErrCacheMiss { //init stripe c := appengine.NewContext(r) stripe.SetBackend(stripe.APIBackend, nil) stripe.SetHTTPClient(urlfetch.Client(c)) //get charge data chg, err = charge.Get(chargeId, nil) if err != nil { fmt.Fprint(w, "An error occured and the receipt cannot be displayed.\n") fmt.Fprint(w, err) return } //save to memcache //just in case we want to view the receipt again memcacheutils.Save(c, chg.ID, chg) } //extract charge data d := chargeutils.ExtractData(chg) //get company info info, err := getCompanyInfo(r) name, street, suite, city, state, postal, country, phone := "", "", "", "", "", "", "", "" if err == ErrCompanyDataDoesNotExist { name = "**Company info has not been set yet.**" street = "**Please contact an administrator to fix this.**" } else { name = info.CompanyName street = info.Street suite = info.Suite city = info.City state = info.State postal = info.PostalCode country = info.Country phone = info.PhoneNum } //display receipt output := templateData{ CompanyName: name, Street: street, Suite: suite, City: city, State: state, Postal: postal, Country: country, PhoneNum: phone, Customer: d.Customer, Cardholder: d.Cardholder, CardBrand: d.CardBrand, LastFour: d.LastFour, Expiration: d.Expiration, Captured: d.CapturedStr, Timestamp: d.Timestamp, Amount: d.AmountDollars, Invoice: d.Invoice, Po: d.Po, } templates.Load(w, "receipt", output) return }
//Main loads the main UI of the app //this is the page the user sees once they are logged in //this ui is a single page app and holds almost all the functionality of the app //the user only sees the parts of the ui they have access to...the rest is removed via go's contemplating //we also check if this page was loaded with a bunch of extra data in the url...this would be used to perform the api-like semi-automated charging of the card // if a link to the page has a "customer_id" form value, this will automatically find the customer's card data and show it in the panel // if "amount", "invoice", and/or "po" form values are given, these will also automatically be filled into the charge panel's form // if "customer_id" is not given, no auto filling will occur of any fields // "amount" must be in cents // card is not automatically charged, user still has to click "charge" button func Main(w http.ResponseWriter, r *http.Request) { //placeholder for sending data back to template var tempData autoloader //get logged in user data //catch instances where session is not working and redirect user to log in page //use the user's data to show/hide certain parts of the ui per the users access rights session := sessionutils.Get(r) if session.IsNew == true { notificationPage(w, "panel-danger", "Cannot Load Page", "Your session has expired or there is an error. Please try logging in again or contact an administrator.", "btn-default", "/", "Log In") return } userId := session.Values["user_id"].(int64) c := appengine.NewContext(r) user, err := users.Find(c, userId) if err != nil { notificationPage(w, "panel-danger", "Cannot Load Page", err, "btn-default", "/", "Try Again") return } //check for url form values for autofilling charge panel //if data in url does not exist, just load the page with user data only custId := r.FormValue("customer_id") if len(custId) == 0 { tempData.UserData = user templates.Load(w, "main", tempData) return } //data in url does exist //look up card data by customer id //get the card data to show in the panel so user can visually confirm they are charging the correct card //if an error occurs, just load the page normally custData, err := card.FindByCustId(c, custId) if err != nil { tempData.Error = "The form could not be autofilled because the customer ID you provided could not be found. The ID is either incorrect or the customer's credit card has not been added yet." tempData.UserData = user templates.Load(w, "main", tempData) return } tempData.CardData = custData tempData.UserData = user //if amount was given, it is in cents //display it in html input as dollars amountUrl := r.FormValue("amount") amountFloat, _ := strconv.ParseFloat(amountUrl, 64) amountDollars := amountFloat / 100 tempData.Amount = amountDollars //check for other form values and build template tempData.Invoice = r.FormValue("invoice") tempData.Po = r.FormValue("po") //load the page with the card data templates.Load(w, "main", tempData) return }
//Report gets the data for charges and refunds by the defined filters (date range and customer) and builds the reports page //the reports show up in a different page so they are easily printable and more easily inspected //date range is inclusive of start and end days func Report(w http.ResponseWriter, r *http.Request) { //get form valuess datastoreId := r.FormValue("customer-id") startString := r.FormValue("start-date") endString := r.FormValue("end-date") hoursToUTC := r.FormValue("timezone") //get report data form stripe //make sure inputs are given if len(startString) == 0 { output.Error(ErrMissingInput, "You must supply a 'start-date'.", w, r) return } if len(endString) == 0 { output.Error(ErrMissingInput, "You must supply a 'end-date'.", w, r) return } if len(hoursToUTC) == 0 { output.Error(ErrMissingInput, "You must supply a 'timezone'.", w, r) return } //get timezone offset //adjust for the local timezone the user is in so that the date range is correct //hoursToUTC is a number generated by JS (-4 for EST) tzOffset := calcTzOffset(hoursToUTC) //get datetimes from provided strings startDt, err := time.Parse("2006-01-02 -0700", startString+" "+tzOffset) if err != nil { output.Error(err, "Could not convert start date to a time.Time datetime.", w, r) return } endDt, err := time.Parse("2006-01-02 -0700", endString+" "+tzOffset) if err != nil { output.Error(err, "Could not convert end date to a time.Time datetime.", w, r) return } //get end of day datetime //need to get 23:59:59 so we include the whole day endDt = endDt.Add((24*60-1)*time.Minute + (59 * time.Second)) //get unix timestamps //stripe only accepts timestamps for filtering charges startUnix := startDt.Unix() endUnix := endDt.Unix() //init stripe c := appengine.NewContext(r) sc := createAppengineStripeClient(c) //retrieve data from stripe //date is a range inclusive of the days the user chose //limit of 100 is the max per stripe params := &stripe.ChargeListParams{} params.Filters.AddFilter("created", "gte", strconv.FormatInt(startUnix, 10)) params.Filters.AddFilter("created", "lte", strconv.FormatInt(endUnix, 10)) params.Filters.AddFilter("limit", "", "100") //check if we need to filter by a specific customer //look up stripe customer id by the datastore id if len(datastoreId) != 0 { datastoreIdInt, _ := strconv.ParseInt(datastoreId, 10, 64) custData, err := findByDatastoreId(c, datastoreIdInt) if err != nil { output.Error(err, "An error occured and this report could not be generated.", w, r) return } params.Filters.AddFilter("customer", "", custData.StripeCustomerToken) } //get results //loop through each charge and extract charge data //add up total amount of all charges charges := sc.Charges.List(params) data := make([]chargeutils.Data, 0, 10) var amountTotal uint64 = 0 var numCharges uint16 = 0 for charges.Next() { //get each charges data chg := charges.Charge() d := chargeutils.ExtractData(chg) //make sure this charge was captured //do not count charges that failed if d.Captured == false { continue } data = append(data, d) //increment totals amountTotal += d.AmountCents numCharges++ } //convert total amount to dollars amountTotalDollars := strconv.FormatFloat((float64(amountTotal) / 100), 'f', 2, 64) //retrieve refunds eventParams := &stripe.EventListParams{} eventParams.Filters.AddFilter("created", "gte", strconv.FormatInt(startUnix, 10)) eventParams.Filters.AddFilter("created", "lte", strconv.FormatInt(endUnix, 10)) eventParams.Filters.AddFilter("limit", "", "100") eventParams.Filters.AddFilter("type", "", "charge.refunded") events := sc.Events.List(eventParams) refunds := chargeutils.ExtractRefunds(events) //get logged in user's data //for determining if receipt/refund buttons need to be hidden or shown based on user's access rights session := sessionutils.Get(r) userId := session.Values["user_id"].(int64) userdata, _ := users.Find(c, userId) //store data for building template result := reportData{ UserData: userdata, StartDate: startDt, EndDate: endDt, Charges: data, Refunds: refunds, TotalAmount: amountTotalDollars, NumCharges: numCharges, } //build template to display report //separate page in gui templates.Load(w, "report", result) return }