/// <summary> /// Creates a single discount in Shopify /// </summary> public static void CreateDiscount(AuthInfo authInfo, Discount discount) { try { Console.Write("Creating {0}: ", discount.Code); string discountsUrl = String.Format(_discountsUrl, authInfo.StoreName,""); HttpWebRequest request = HttpWebRequest.Create(discountsUrl) as HttpWebRequest; // Set required HTTP headers request.Accept = "*/*"; request.Headers["Accept-Language"] = "en-US"; request.Headers["Accept-Charset"] = "utf-8"; request.Headers["X-Requested-With"] = "XMLHttpRequest"; request.Headers["X-Prototype-Version"] = "1.7"; request.Headers["Cookie"] = String.Format("_secure_session_id={0}", authInfo.SessionId); request.Headers["X-CSRF-Token"] = authInfo.CsrfToken; // POST the create discount form IDictionary<string, string> postData = new Dictionary<string, string>(); postData["authenticity_token"] = authInfo.CsrfToken; postData["discount[code]"] = discount.Code; postData["type"] = discount.Type; postData["discount[value]"] = discount.Value; postData["discount[applies_to_type]"] = discount.AppliesToType; postData["discount[minimum_order_amount]"] = discount.MinimumOrderAmount; postData["discount[applies_to_id]"] = discount.AppliesToId; postData["discount[starts_at]"] = discount.StartsAt; postData["discount[ends_at]"] = discount.EndsAt; postData["discount[usage_limit]"] = discount.UsageLimit; HttpWebResponse response = request.Post(postData); // Read response in. string responseText = response.GetResponseText(); // Check for application level errors if (responseText.Contains("Messenger.error(\"")) { string errorMessage = responseText.GetFirstDelimitedValue("Messenger.error(\"", "\");"); throw new Exception(errorMessage); } // Check for success string successNotice = String.Format("Messenger.notice(\"Successfully created the discount {0}\\u0026hellip;\");", discount.Code); if (!responseText.Contains(successNotice)) { throw new Exception("Sanity check failed"); } Console.WriteLine("OK"); } catch (Exception ex) { Console.WriteLine("ERROR"); string errorMessage = String.Format("Error Creating Discount ({0})", ( discount == null ? "" : discount.Code) ); throw new Exception(errorMessage, ex); } }
/// <summary> /// Posts the Shopify store login page and populates the authInfo object with the /// session and CSRF tokens parsed from the reponse. /// </summary> public static void Authenticate(AuthInfo authInfo) { string loginUrl = null; try { loginUrl = String.Format(_loginUrl, authInfo.StoreName); HttpWebRequest request = HttpWebRequest.Create(loginUrl) as HttpWebRequest; request.CookieContainer = new CookieContainer(); // POST the login form IDictionary<string, string> postData = new Dictionary<string, string>(); postData["login"] = authInfo.Username; postData["password"] = authInfo.Password; HttpWebResponse response = request.Post(postData); // Get the session cookie Cookie sessionIdCookie = request.CookieContainer.GetCookies(new Uri(loginUrl))["_secure_session_id"]; authInfo.SessionId = sessionIdCookie.Value; // Read response in HtmlDocument html = response.GetResponseHtml(); // Do a quick sanity check to make sure that we are logged in. // (If there is a logout link, its a safe bet to assume that we are logged in) HtmlNode logoutLink = html.DocumentNode.SelectSingleNode("//a[@href=\"/admin/auth/logout\"]"); if (logoutLink == null) { throw new Exception("Login Failed"); } // Pluck out the csrf token. HtmlNode hiddenCsrfInput = html.DocumentNode.SelectSingleNode("//meta[@name=\"csrf-token\"]"); authInfo.CsrfToken = WebUtility.HtmlDecode(hiddenCsrfInput.Attributes["content"].Value); } catch (Exception ex) { throw new Exception("Error logging into shopify", ex); } }
/// <summary> /// Creates multiple discounts in Shopify /// </summary> public static void CreateDiscounts(AuthInfo authInfo, IEnumerable<Discount> discounts) { bool errors = false; foreach (Discount discount in discounts) { // Log errors and continue. We rethrow later. try { DiscountsWrapper.CreateDiscount(authInfo, discount); } catch (Exception ex) { errors = true; _log.Error(ex); } } if(errors) { throw new Exception("One or more errors occurred while creating discounts"); } }
/// <summary> /// Reads all discounts from Shopify /// </summary> public static void ReadDiscounts(AuthInfo authInfo, IList<Discount> discounts) { bool errors = false; for (int pageNumber = 1; true; pageNumber++) { try { Console.WriteLine("Processing Page {0}", pageNumber); string marketingUrl = String.Format(_marketingUrl, authInfo.StoreName, pageNumber); HttpWebRequest request = HttpWebRequest.Create(marketingUrl) as HttpWebRequest; // Set required HTTP headers request.Accept = "*/*"; request.Headers["Accept-Language"] = "en-US"; request.Headers["Accept-Charset"] = "utf-8"; request.Headers["Cookie"] = String.Format("_secure_session_id={0}", authInfo.SessionId); request.Headers["X-CSRF-Token"] = authInfo.CsrfToken; // Read response HttpWebResponse response = (HttpWebResponse)request.GetResponse(); HtmlDocument html = response.GetResponseHtml(); // Parse out the discounts rows from the Coupons table HtmlNodeCollection discountRows = html.DocumentNode.SelectNodes("//table[@id=\"coupons\"]/tbody/tr"); // If the first row does not have 4 columns, we no discounts for this page. if (discountRows[0].SelectNodes("td").Count != 4) { break; } // Each row is a discount. foreach (HtmlNode discountRow in discountRows) { // Log errors and continue. We rethrow later. try { Discount discount = ParseDiscount(discountRow); discounts.Add(discount); } catch (Exception ex) { errors = true; _log.Error(ex); } } } catch (Exception ex) { string errorMessage = String.Format("Error Processing Page {0}", pageNumber); throw new Exception(errorMessage, ex); } } if (errors) { throw new Exception("One or more errors occurred while reading discounts"); } }
/// <summary> /// Main program entry point /// </summary> public static void Main(string[] args) { try { // Ignore Certificate validation failures (aka untrusted certificate + certificate chains) so we can debug https in Fiddler //ServicePointManager.ServerCertificateValidationCallback = ((sender2, certificate, chain, sslPolicyErrors) => true); string helpText = String.Empty; helpText += "\r\n"; helpText += "EZSD.EXE storename username password [/CREATE | /READ | /DELETE] csvfile\r\n"; helpText += " storename Specifies the Shopify store name\r\n"; helpText += " username Specifies the user name used to _log into Shopify\r\n"; helpText += " password Specifies the password used to _log into Shopify\r\n"; helpText += " /CREATE Causes discounts to be created in Shopify\r\n"; helpText += " /READ Causes discounts to be read from Shopify\r\n"; helpText += " /DELETE Causes discounts to be deleted in Shopify\r\n"; helpText += " csvfile Specifies the CSV csvFile to be read from or written to\r\n"; helpText += "\r\n"; // Validate Arguments Length if (args.Length != 5) { Console.WriteLine(helpText); return; } // Validate StoreName, Username, and Password if (args[0].Length < 1 || args[1].Length < 1 || args[2].Length < 1) { Console.WriteLine(helpText); return; } // Validate Action string action = args[3].ToUpper(); if (action != "/CREATE" && action != "/READ" && action != "/DELETE") { Console.WriteLine(helpText); return; } // Validate Filename string csvFile = null; try { csvFile = Path.GetFullPath(args[4]); } catch(Exception) { Console.WriteLine(helpText); return; } // Authenticate with shopify AuthInfo authInfo = new AuthInfo() { StoreName = args[0], Username = args[1], Password = args[2] }; Console.WriteLine("Authenticating..."); AuthWrapper.Authenticate(authInfo); switch (action) { case "/CREATE": Console.WriteLine("Creating Discounts..."); CreateDiscounts(authInfo, csvFile); break; case "/READ": Console.WriteLine("Reading Discounts..."); ReadDiscounts(authInfo, csvFile); break; case "/DELETE": Console.WriteLine("Deleting Discounts..."); DeleteDiscounts(authInfo, csvFile); break; } Console.WriteLine("Done!"); } catch (Exception ex) { _log.Error(ex); Console.WriteLine("Done, but with errors. Check ezsd.log.txt for details."); } }
/// <summary> /// Helper method to read discounts /// </summary> private static void ReadDiscounts(AuthInfo authInfo, string csvFile) { List<Discount> discounts = new List<Discount>(); DiscountsWrapper.ReadDiscounts(authInfo, discounts); Console.WriteLine("Writing Discounts to file..."); using (TextWriter writer = new StreamWriter(csvFile)) { // Write file header writer.WriteLine(Discount.CsvFileHeader); // Write discounts FileHelperEngine fileHelper = new FileHelperEngine(typeof(Discount)); fileHelper.WriteStream(writer, discounts); } }
/// <summary> /// Helper method to delete discounts /// </summary> private static void DeleteDiscounts(AuthInfo authInfo, string csvFile) { // Read in discount codes from csvFile FileHelperEngine fileHelper = new FileHelperEngine(typeof(Discount)); Discount[] discounts = fileHelper.ReadFile(csvFile) as Discount[]; // Add discount codes for csvFile DiscountsWrapper.DeleteDiscounts(authInfo, discounts); }