package main import ( "bytes" "context" "fmt" "html/template" "log" "net/http" "path" "regexp" "strconv" "github.com/gin-gonic/gin" "gopkg.in/gomail.v2" ) const SHIPPING = 675 const SHIPPING_INT = 930 var countries = map[string]string{ "AF": "Afghanistan", "AX": "Ă…land Islands", "AL": "Albania", "DZ": "Algeria", "AS": "American Samoa", "AD": "AndorrA", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarctica", "AG": "Antigua and Barbuda", "AR": "Argentina", "AM": "Armenia", "AW": "Aruba", "AU": "Australia", "AT": "Austria", "AZ": "Azerbaijan", "BS": "Bahamas", "BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BY": "Belarus", "BE": "Belgium", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnia and Herzegovina", "BW": "Botswana", "BV": "Bouvet Island", "BR": "Brazil", "IO": "British Indian Ocean Territory", "BN": "Brunei Darussalam", "BG": "Bulgaria", "BF": "Burkina Faso", "BI": "Burundi", "KH": "Cambodia", "CM": "Cameroon", "CA": "Canada", "CV": "Cape Verde", "KY": "Cayman Islands", "CF": "Central African Republic", "TD": "Chad", "CL": "Chile", "CN": "China", "CX": "Christmas Island", "CC": "Cocos (Keeling) Islands", "CO": "Colombia", "KM": "Comoros", "CG": "Congo", "CD": "Congo, The Democratic Republic of the", "CK": "Cook Islands", "CR": "Costa Rica", "CI": "Cote D'Ivoire", "HR": "Croatia", "CU": "Cuba", "CY": "Cyprus", "CZ": "Czech Republic", "DK": "Denmark", "DJ": "Djibouti", "DM": "Dominica", "DO": "Dominican Republic", "EC": "Ecuador", "EG": "Egypt", "SV": "El Salvador", "GQ": "Equatorial Guinea", "ER": "Eritrea", "EE": "Estonia", "ET": "Ethiopia", "FK": "Falkland Islands (Malvinas", "FO": "Faroe Islands", "FJ": "Fiji", "FI": "Finland", "FR": "France", "GF": "French Guiana", "PF": "French Polynesia", "TF": "French Southern Territories", "GA": "Gabon", "GM": "Gambia", "GE": "Georgia", "DE": "Germany", "GH": "Ghana", "GI": "Gibraltar", "GR": "Greece", "GL": "Greenland", "GD": "Grenada", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard Island and Mcdonald Islands", "VA": "Holy See (Vatican City State", "HN": "Honduras", "HK": "Hong Kong", "HU": "Hungary", "IS": "Iceland", "IN": "India", "ID": "Indonesia", "IR": "Iran, Islamic Republic Of", "IQ": "Iraq", "IE": "Ireland", "IM": "Isle of Man", "IL": "Israel", "IT": "Italy", "JM": "Jamaica", "JP": "Japan", "JE": "Jersey", "JO": "Jordan", "KZ": "Kazakhstan", "KE": "Kenya", "KI": "Kiribati", "KP": "Korea, Democratic People'S Republic of", "KR": "Korea, Republic of", "KW": "Kuwait", "KG": "Kyrgyzstan", "LA": "Lao People'S Democratic Republic", "LV": "Latvia", "LB": "Lebanon", "LS": "Lesotho", "LR": "Liberia", "LY": "Libyan Arab Jamahiriya", "LI": "Liechtenstein", "LT": "Lithuania", "LU": "Luxembourg", "MO": "Macao", "MK": "Macedonia, The Former Yugoslav Republic of", "MG": "Madagascar", "MW": "Malawi", "MY": "Malaysia", "MV": "Maldives", "ML": "Mali", "MT": "Malta", "MH": "Marshall Islands", "MQ": "Martinique", "MR": "Mauritania", "MU": "Mauritius", "YT": "Mayotte", "MX": "Mexico", "FM": "Micronesia, Federated States of", "MD": "Moldova, Republic of", "MC": "Monaco", "MN": "Mongolia", "MS": "Montserrat", "MA": "Morocco", "MZ": "Mozambique", "MM": "Myanmar", "NA": "Namibia", "NR": "Nauru", "NP": "Nepal", "NL": "Netherlands", "AN": "Netherlands Antilles", "NC": "New Caledonia", "NZ": "New Zealand", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue", "NF": "Norfolk Island", "MP": "Northern Mariana Islands", "NO": "Norway", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PS": "Palestinian Territory, Occupied", "PA": "Panama", "PG": "Papua New Guinea", "PY": "Paraguay", "PE": "Peru", "PH": "Philippines", "PN": "Pitcairn", "PL": "Poland", "PT": "Portugal", "PR": "Puerto Rico", "QA": "Qatar", "RE": "Reunion", "RO": "Romania", "RU": "Russian Federation", "RW": "RWANDA", "SH": "Saint Helena", "KN": "Saint Kitts and Nevis", "LC": "Saint Lucia", "PM": "Saint Pierre and Miquelon", "VC": "Saint Vincent and the Grenadines", "WS": "Samoa", "SM": "San Marino", "ST": "Sao Tome and Principe", "SA": "Saudi Arabia", "SN": "Senegal", "CS": "Serbia and Montenegro", "SC": "Seychelles", "SL": "Sierra Leone", "SG": "Singapore", "SK": "Slovakia", "SI": "Slovenia", "SB": "Solomon Islands", "SO": "Somalia", "ZA": "South Africa", "GS": "South Georgia and the South Sandwich Islands", "ES": "Spain", "LK": "Sri Lanka", "SD": "Sudan", "SR": "Suriname", "SJ": "Svalbard and Jan Mayen", "SZ": "Swaziland", "SE": "Sweden", "CH": "Switzerland", "SY": "Syrian Arab Republic", "TW": "Taiwan, Province of China", "TJ": "Tajikistan", "TZ": "Tanzania, United Republic of", "TH": "Thailand", "TL": "Timor-Leste", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad and Tobago", "TN": "Tunisia", "TR": "Turkey", "TM": "Turkmenistan", "TC": "Turks and Caicos Islands", "TV": "Tuvalu", "UG": "Uganda", "UA": "Ukraine", "AE": "United Arab Emirates", "GB": "United Kingdom", "US": "United States", "UM": "United States Minor Outlying Islands", "UY": "Uruguay", "UZ": "Uzbekistan", "VU": "Vanuatu", "VE": "Venezuela", "VN": "Viet Nam", "VG": "Virgin Islands, British", "VI": "Virgin Islands, U.S", "WF": "Wallis and Futuna", "EH": "Western Sahara", "YE": "Yemen", "ZM": "Zambia", "ZW": "Zimbabwe", } type orderPayload struct { Products []int `form:"product"` Name string `form:"name"` Streetname string `form:"streetname"` Postcode string `form:"postcode"` EmailAddress string `form:"email_address"` City string `form:"city"` } func sendMail(preOrder PreOrder, config Config) { tpl, err := template.ParseFiles(path.Join(ROOT_DIR, "templates/preorder.email")) if err != nil { panic(err) } m := gomail.NewMessage() var w bytes.Buffer data := map[string]interface{}{ "preOrder": preOrder, } tpl.Execute(&w, data) m.SetHeader("From", config.FromAddr) m.SetHeader("To", preOrder.Email) m.SetAddressHeader("Bcc", config.BCC, "admin") m.SetHeader("Subject", "Your Thomas Pol Shop order") m.SetBody("text/html", w.String()) d := gomail.NewDialer(config.MailServer, config.MailPort, config.MailUser, config.MailPass) // Send the email to Bob, Cora and Dan. if err := d.DialAndSend(m); err != nil { panic(err) } sendAdminMail(preOrder, config) } func sendAdminMail(preOrder PreOrder, config Config) { tpl, err := template.New("tpl").Funcs(template.FuncMap{ "countries": func() map[string]string { return countries }, }).ParseFiles(path.Join(ROOT_DIR, "templates/preorder_admin.email")) if err != nil { panic(err) } m := gomail.NewMessage() var w bytes.Buffer data := map[string]interface{}{ "preOrder": preOrder, } tpl.Execute(&w, data) m.SetHeader("From", config.FromAddr) m.SetHeader("To", config.BCC) m.SetAddressHeader("Bcc", config.BCC, "admin") m.SetHeader("Subject", "Thomas Pol order") m.SetBody("text/html", w.String()) d := gomail.NewDialer(config.MailServer, config.MailPort, config.MailUser, config.MailPass) // Send the email to Bob, Cora and Dan. if err := d.DialAndSend(m); err != nil { panic(err) } } var prodRegexp = regexp.MustCompile(`products\[(\d+)\]`) func parseProducts(c *gin.Context) map[uint]int { prods := map[uint]int{} c.Request.ParseForm() for k, v := range c.Request.Form { if m := prodRegexp.FindAllStringSubmatch(k, 1); len(m) > 0 { id, _ := strconv.ParseInt(m[0][1], 10, 64) n, _ := strconv.ParseInt(v[0], 10, 64) prods[uint(id)] = int(n) } } return prods } type Shipping struct { Name string Email string Address string Postcode string City string Country string } func (s Shipping) Valid() bool { if s.Name == "" { return false } if s.Email == "" { return false } if s.Address == "" { return false } if s.Postcode == "" { return false } if s.City == "" { return false } if s.Country == "" { return false } return true } func parseShipping(c *gin.Context) Shipping { shipping := Shipping{} c.Request.ParseForm() for k, v := range c.Request.Form { switch k { case "name": shipping.Name = v[0] case "email": shipping.Email = v[0] case "address": shipping.Address = v[0] case "postcode": shipping.Postcode = v[0] case "city": shipping.City = v[0] case "country": shipping.Country = v[0] } } if shipping.Country == "" { shipping.Country = "NL" } return shipping } func postGetOrderInfo(c *gin.Context) { db := getDB(c) products := []Product{} db.Find(&products) c.Request.ParseForm() prods := parseProducts(c) shipping := parseShipping(c) total := 0 for p, q := range prods { cur := getProd(p, products) if cur == nil { continue } total += q * cur.Price } c.HTML(http.StatusOK, "checkout", map[string]interface{}{ "error": "", "total": total, "products": products, "shipping": shipping, "order": prods, }) } func postCheckout(c *gin.Context) { db := getDB(c) products := []Product{} db.Find(&products) c.Request.ParseForm() prods := parseProducts(c) total := 0 for p, q := range prods { cur := getProd(p, products) if cur == nil { continue } total += q * cur.Price } shipping := parseShipping(c) if !shipping.Valid() { c.HTML(http.StatusOK, "checkout", map[string]interface{}{ "error": "All fields are required", "total": total, "products": products, "shipping": shipping, "order": prods, }) return } shippingCost := SHIPPING shippingLabel := "Shipping NL" if shipping.Country != "NL" { shippingCost = SHIPPING_INT shippingLabel = "Shipping international" } total += shippingCost c.HTML(http.StatusOK, "orderInfo", map[string]interface{}{ "total": total, "products": products, "shipping": shipping, "shippingCost": shippingCost, "shippingLabel": shippingLabel, "order": prods, }) } func createOrder(c *gin.Context) { db := getDB(c) products := []Product{} db.Find(&products) c.Request.ParseForm() prods := parseProducts(c) shipping := parseShipping(c) preOrder := PreOrder{ Name: shipping.Name, Email: shipping.Email, Address: shipping.Address, Postcode: shipping.Postcode, City: shipping.City, Country: shipping.Country, Status: PRE_ORDER_STATE_CREATED, } db.Create(&preOrder) for p, q := range prods { for i := 0; i < q; i++ { db.Create(&Item{ ProductID: p, PreOrderID: preOrder.ID, }) } } shippingCost := SHIPPING if shipping.Country != "NL" { shippingCost = SHIPPING_INT } db.Preload("Items").Preload("Items.Product").Where("id = ?", preOrder.ID).Find(&preOrder) _, payment, err := createMolliePayment(preOrder, shippingCost) if err != nil { log.Printf("Something went wrong creating payment: %v", err) c.AbortWithError(500, fmt.Errorf("I'am sorry, something went wrong...:(")) return } molliePayment := MolliePayment{ MolliePaymentID: payment.ID, Amount: preOrder.CalcTotal() + shippingCost, Status: payment.Status, PreOrderID: preOrder.ID, } db.Create(&molliePayment) c.Redirect(http.StatusFound, payment.Links.Checkout.Href) } func postMollieWebhook(c *gin.Context) { db := getDB(c) paymentID := c.Request.FormValue("id") _, payment, err := mollieClient.Payments.Get(context.Background(), paymentID, nil) if err != nil { log.Printf("Error get payment from mollie; %v; %s", err, paymentID) c.AbortWithError(500, fmt.Errorf("Deze bestelling is onbekend")) return } molliePayment := &MolliePayment{} if err := db.Preload("PreOrder").Where("mollie_payment_id = ?", paymentID).Find(&molliePayment).Error; err != nil { log.Printf("Error get payment from db; %v; %s", err, paymentID) c.AbortWithError(500, fmt.Errorf("Unknown order")) return } if payment.Status != molliePayment.Status { molliePayment.Status = payment.Status db.Save(&molliePayment) preOrder := PreOrder{} db.Preload("Items").Preload("Items.Product").Where("id = ?", molliePayment.PreOrderID).Find(&preOrder) if molliePayment.Status == "paid" { sendMail(preOrder, config) } } c.Status(200) } func postOrderRetry(c *gin.Context) { orderID := c.Param("orderid") db := getDB(c) preOrder := &PreOrder{} if err := db.Preload("Items").Preload("Items.Product").Where("id = ?", orderID).Find(&preOrder).Error; err != nil { c.AbortWithError(500, fmt.Errorf("This order is unknown")) return } if preOrder.ID.String() == nullUUID.String() { c.AbortWithError(500, fmt.Errorf("Unknown Order")) return } db.Find(&preOrder) shippingCost := SHIPPING if preOrder.Country != "NL" { shippingCost = SHIPPING_INT } _, payment, err := createMolliePayment(*preOrder, shippingCost) if err != nil { c.AbortWithError(500, fmt.Errorf("Sorry, something went wrong...:(")) return } molliePayment := MolliePayment{ MolliePaymentID: payment.ID, Amount: preOrder.CalcTotal() + SHIPPING, Status: payment.Status, PreOrderID: preOrder.ID, } db.Create(&molliePayment) c.Redirect(http.StatusFound, payment.Links.Checkout.Href) } func getPendingOrder(c *gin.Context) { orderID := c.Param("orderid") db := getDB(c) payments := []*MolliePayment{} if err := db.Where("pre_order_id = ?", orderID).Order("created_at desc").Find(&payments).Error; err != nil { c.AbortWithError(500, fmt.Errorf("Unknown Order")) return } if len(payments) == 0 { c.AbortWithError(500, fmt.Errorf("Unknown Order")) return } payment := payments[0] if payment.Status == "paid" { c.Redirect(http.StatusFound, "/order/thankyou") return } c.HTML(http.StatusOK, "orderPending", map[string]interface{}{"payment": payment, "orderID": orderID}) } func getPreOrderThankYou(c *gin.Context) { c.HTML(http.StatusOK, "orderThankyou", nil) }