// GET: /account/paypal-payment public ActionResult PaypalPayment(string tx = null) { PayPalPaymentResult result = PayPalPaymentResult.Canceled; var helper = new PayPalHelper(); var txnId = !String.IsNullOrEmpty(tx) // tx in PDT from Sandbox is upper-case on automatic return and lower-case on manual return. A PDT request returns error 4002 if given a low-case tx. ? tx.ToUpper() : "PDT " + KeyUtils.GetCurrentTimeKey(); helper.WriteLog(PayPalLogEntity.NotificationKind.PDT, txnId, Request.Url.Query); if (!String.IsNullOrEmpty(tx)) { // Query PayPal. var response = helper.RequestPaymentDetails(txnId); var logRowKey = helper.WriteLog(PayPalLogEntity.NotificationKind.DetailsRequest, txnId, response); // Parse the response var lines = helper.SplitPdtMessage(response); var payment = helper.ParsePaymentLines(lines); // Write the payment to the database. if (helper.PostIncomingPayPalPayment(payment, logRowKey)) { result = PayPalPaymentResult.Success; } } return(RedirectToAction("Transactions", new { PayPalPaymentResult = result })); }
public async Task <IHttpActionResult> PostResourceView([FromUri] int id, [FromBody] JObject value) { // A suggestion may have resourceId == null(0?) when the resource is made on the client in code and points to Google // TODO. Idea: Get the unauthenicated user's session cookie and log views for the session in a separate datastore. var userId = this.GetUserId(); // GetUserId() returns 0 for an unauthenticated user. That's fine. We log every view. var logEnity = new DynamicTableEntity(KeyUtils.GetCurrentTimeKey(), KeyUtils.IntToKey(userId)); logEnity["Json"] = new EntityProperty(value.ToString()); var logTask = AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.LibraryLog, logEnity); if (userId != 0 && id != 0) { var viewTask = DapperHelper.ExecuteResilientlyAsync("dbo.libPostResourceView", new { UserId = userId, ResourceId = id, }, CommandType.StoredProcedure); // We use KeyUtils.LocalTimeToInvertedKey() to keep the local time and to order last records first for retrieval. var localTime = (string)value["localTime"]; var rowKey = KeyUtils.LocalTimeToInvertedKey(localTime); if (String.IsNullOrEmpty(rowKey)) { throw new ArgumentOutOfRangeException(localTime); } var historyEntity = new LibraryHistoryEntity { PartitionKey = KeyUtils.IntToKey(userId), RowKey = rowKey, ResourceId = id, }; var historyTask = AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.LibraryHistory, historyEntity); // Do all the tasks in parallel. await Task.WhenAll(viewTask, historyTask, logTask); } else { await logTask; } return(StatusCode(HttpStatusCode.NoContent)); }
public async Task <IHttpActionResult> InboundWebhook(InboundWebhookFormModel form) { // The form is empty on a HEAD request if (form != null) { var mandrill_events = form.mandrill_events; var partitionKey = KeyUtils.GetCurrentTimeKey(); var entity = new ExternalSessionLogEntity { PartitionKey = partitionKey, RowKey = "InboundWebhook", Data = mandrill_events, }; await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity); await SendSmsMessage("InboundWebhook " + partitionKey, partitionKey); } return(StatusCode(HttpStatusCode.NoContent)); }
public string WriteLog(PayPalLogEntity.NotificationKind kind, string txnId, string logData) { if (String.IsNullOrEmpty(txnId)) { throw new ArgumentException(); } var rowKey = KeyUtils.GetCurrentTimeKey(); var entity = new PayPalLogEntity { PartitionKey = txnId, RowKey = rowKey, Kind = kind.ToString(), LogData = logData, }; AzureStorageUtils.InsertEntity(AzureStorageUtils.TableNames.PaymentLog, entity); return(rowKey); }
// Extract text from the HTML page private string FindText(string html, string startSnippet, string endSnippet) { var i1 = html.IndexOf(startSnippet); var i2 = html.IndexOf(endSnippet, i1 + 1); if ((i1 == -1) || (i2 == -1)) { // Write the poison HTML page to the log. var partitionKey = KeyUtils.GetCurrentTimeKey(); var entity = new ExternalSessionLogEntity { PartitionKey = partitionKey, RowKey = "Error_FindText", Data = html, }; AzureStorageUtils.InsertEntity(AzureStorageUtils.TableNames.ExternalSessions, entity); throw new FormatException("Parsing the external service data. Snippet not found. " + startSnippet.Length.ToString() + " " + endSnippet.Length.ToString()); } var shift = startSnippet.Length; return(html.Substring(i1 + shift, i2 - i1 - shift)); }
public async Task <IHttpActionResult> PostBooking([FromBody] JObject json) { TimeUtils.ThrowIfClientTimeIsAmbiguous((string)json["localTime"], (int)json["localTimezoneOffset"]); var teacherUserId = (int)json["teacherUserId"]; var start = (DateTime)json["start"]; var end = (DateTime)json["end"]; var price = (decimal)json["price"]; var userId = this.GetUserId(); var teacher = SessionHelper.ItalkiTeachers.First(i => i.UserId == teacherUserId); var periods = await SessionHelper.GetTeacherPeriods(teacherUserId, true); var available = periods.Any(i => (i.Start <= start) && (i.End >= end)); if (!available) { throw new Exception(ItalkiHelper.TimeSlotUnavailableError); } /* Compensation is the mechanism by which previously completed work can be undone or compensated when a subsequent failure occurs. * The Compensation pattern is required in error situations when multiple atomic operations cannot be linked with classic transactions. */ // We cannot use a transaction in a heterogeneous storage mediums. An orphaned message on Azure Table is not a problem, we do not compensate it on error. The main message metadata is in the database anyway. var message = (string)json["message"]; var messageExtId = await SessionHelper.SaveMessageAndGetExtId(message); var sessionId = (await DapperHelper.QueryResilientlyAsync <int>("dbo.sesBookSession", new { UserId = userId, // who requests and pays Start = start, End = end, TeacherUserId = teacherUserId, Price = price, MessageExtId = messageExtId, }, CommandType.StoredProcedure)) .Single(); // We will write to the log. var partitionKey = KeyUtils.GetCurrentTimeKey(); // Book the session on Italki. try { long?sessionExtId = null; #if DEBUG sessionExtId = 0; #else var helper = new ItalkiHelper(); sessionExtId = await helper.BookSession(teacher.CourseId, start, teacher.ScheduleUrl, partitionKey); #endif if (sessionExtId.HasValue) { await DapperHelper.ExecuteResilientlyAsync("dbo.sesUpdateSessionExtId", new { SessionId = sessionId, ExtId = sessionExtId, }, CommandType.StoredProcedure); } } catch (Exception ex) { // Compensation on booking failure. // Clean up the session. DapperHelper.ExecuteResiliently("dbo.sesCancelSession", new { UserId = userId, SessionId = sessionId, ClearUsers = true, }, CommandType.StoredProcedure); throw new UserAlertException("Failed to book session", ex); } HostingEnvironment.QueueBackgroundWorkItem(async(ct) => await SendSmsMessage("Session request. Teacher " + teacherUserId.ToString(), partitionKey)); return(StatusCode(HttpStatusCode.NoContent)); }
public ActionResult PaypalIpn() { var content = Request.BinaryRead(Request.ContentLength); var message = Encoding.ASCII.GetString(content); var helper = new PayPalHelper(); var lines = helper.SplitIpnMessage(message); var payment = helper.ParsePaymentLines(lines); var txnId = !String.IsNullOrEmpty(payment.TxnId) ? payment.TxnId : "IPN " + KeyUtils.GetCurrentTimeKey(); var logRowKey = helper.WriteLog(PayPalLogEntity.NotificationKind.IPN, txnId, message); var response = helper.VerifyIPN(message, txnId); helper.WriteLog(PayPalLogEntity.NotificationKind.IPNResponse, txnId, response); if (response == "VERIFIED") { if (helper.PostIncomingPayPalPayment(payment, logRowKey)) { //helper.PurchaseOnIncomingPayPalPayment(payment); } } return(new HttpStatusCodeResult(HttpStatusCode.NoContent)); }
public async Task <long?> BookSession(int courseId, DateTime start, string referer, string tablePartitionKey) { // Time slot is encoded in a weired way. var origin = DateTime.UtcNow.Date; var diff = start - origin; var day = Convert.ToInt32(Math.Floor(diff.TotalDays)) - 1; // Day number for the next day is 0. if (day < 0) { throw new Exception(ItalkiHelper.TimeSlotUnavailableError); } var halfHour = (diff.Hours * 2) + (diff.Minutes == 30 ? 1 : 0); // Convert.ToInt32(1.0 * diff.Minutes / 30); // We expect minutes to be either 00 or 30. var timeText = String.Format("{0},{1}", day, halfHour); // We will write to the log. var partitionKey0 = KeyUtils.GetCurrentTimeKey(); // Pre-booking validation. It might be redundand. We may want to do it just in case. // Note that the validation call does not send the courseId. That means the server uses a session(???) or a cookie or the Referer header. It may cause problems in a multi-teacher scenario, until we use a separate HttpClient for each teacher. //var content1 = new FormUrlEncodedContent(new[] //{ // new KeyValuePair<string, string>("MethodName", "ValidSessionTime"), // new KeyValuePair<string, string>("time", timeText), //}); //var request1 = new HttpRequestMessage //{ // RequestUri = new Uri(ItalkiWebServicesUrl), // Method = HttpMethod.Post, // Content = content1, //}; //request1.Headers.Add("Referer", referer); //var response1 = await httpClient.SendAsync(request1); //var statusCode1 = (int)response1.StatusCode; //string responseContent1 = null; //if (response1.IsSuccessStatusCode) //{ // responseContent1 = await response1.Content.ReadAsStringAsync(); //} //var entity1 = new ExternalSessionLogEntity //{ // PartitionKey = partitionKey, // RowKey = "ValidationResponse", // Data = responseContent1, // HttpStatus = statusCode1, //}; //await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity1); //response1.EnsureSuccessStatusCode(); // Send the booking request #if DEBUG var password = "******"; #else var password = ConfigurationManager.AppSettings["Italki.Password1"]; #endif var content = new FormUrlEncodedContent(new[] { new KeyValuePair <string, string>("MethodName", "RequestSession"), new KeyValuePair <string, string>("time", timeText), // It will be Url-encoded by the form oject. new KeyValuePair <string, string>("tool", "1"), new KeyValuePair <string, string>("sim", "andrey.fomin3"), new KeyValuePair <string, string>("msg", ":)"), // ":-) :) :D :o) :] :3 :c) :> =] 8) =) :} :^)" new KeyValuePair <string, string>("pwd", password), new KeyValuePair <string, string>("cid", courseId.ToString()), }); var request = new HttpRequestMessage { RequestUri = new Uri(ItalkiWebServicesUrl), Method = HttpMethod.Post, Content = content, }; request.Headers.Add("Referer", referer); // Write to the log var entity = new ExternalSessionLogEntity { PartitionKey = tablePartitionKey, RowKey = "ItalkiRequest", Data = JsonUtils.SerializeAsJson(new { CourseId = courseId, Start = start, Time = timeText, }), }; await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity); // Book the session with Italki var response = await httpClient.SendAsync(request); var statusCode = (int)response.StatusCode; string responseContent = null; if (response.IsSuccessStatusCode) { responseContent = await response.Content.ReadAsStringAsync(); } // Write the response to the log entity = new ExternalSessionLogEntity { PartitionKey = tablePartitionKey, RowKey = "ItalkiResponse", Data = responseContent, HttpStatus = statusCode, }; await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity); response.EnsureSuccessStatusCode(); long?extSessionId = null; try { var items = JArray.Parse(responseContent); /* +https://secure.italki.com/sessions/schedule_20131106.js?v=150302 , Line 853 * if(result[0]=="-2") $get("MsgPrompt").innerHTML=TS312; * if(result[0]=="-1") $get("MsgPrompt").innerHTML=TS313; * if(result[0]=="1") $get("MsgPrompt").innerHTML=TS314; * if(result[0]=="-3") $get("MsgPrompt").innerHTML=TS315; * In HTML * var TS312="The time you\'re scheduling was taken by other students. Please try other time."; * var TS313="Your payment account balance is not enough to pay for the session(s) you are scheduling."; * var TS314="Trial session is not available."; * var TS315="Incorrect password"; */ var result = (string)items.FirstOrDefault(); if ((result == "Y") || (result == "Y1")) { var idArr = items[3]; if (idArr is JArray) { extSessionId = (long)idArr.First(); } } } // We may get an HTML instead of JSON in response. catch (JsonReaderException) { } return(extSessionId); }