private async Task <OrderDBE> InsertNewExplodedOrderAsync(int merchantId, NewOrderMBE newExplodedOrder, int?refOrderId = null) { //Step 1: Store the new merchant Order var newDBOrder = new OrderDBE() { Status = Enums.ORDER_STATUS.New, MerchantId = merchantId, CustomerName = newExplodedOrder.CustomerName, PhoneNumber = newExplodedOrder.CustomerPhoneNo, OrderDateTimeUTC = DateTime.UtcNow, RefOrderId = refOrderId }; await _dbContext.InsertOrderAsync(newDBOrder); //Step 2: create the first event var newDBOrderEvent = new OrderEventDBE() { OrderId = newDBOrder.OrderId, EventDateTimeUTC = DateTime.UtcNow, OrderStatus = Enums.ORDER_STATUS.New, EventDescription = refOrderId.HasValue ? $"Order created (from {refOrderId.Value})." : "Order created" }; await _dbContext.InsertOrderEventAsync(newDBOrderEvent); //newDBOrder.OrderEvents.Add(newDBOrderEvent); // build exploded order //Step 3: iterate through orderLineItems collection to save it in the db foreach (var orderLineItem in newExplodedOrder.OrderLineItems) { var catalogItem = await _dbContext.GetCatalogItemAsync(orderLineItem.ItemGuid); var newDBOrderLineItem = new OrderLineItemDBE() { ItemName = catalogItem.ItemName, ItemUnitPrice = catalogItem.ItemUnitPrice, OrderId = newDBOrder.OrderId, CatalogItemGuid = orderLineItem.ItemGuid }; await _dbContext.InsertOrderLineItemAsync(newDBOrderLineItem); //newDBOrder.OrderLineItems.Add(newDBOrderLineItem); // build exploded order } return(newDBOrder); }
private async Task SendSMSMessageAsync(OrderDBE dbOrderExploded, WebUrlConfigurationBE webUrlConfig) { // Step 1: calc the order total decimal orderTotal = (dbOrderExploded.OrderLineItems != null) ? dbOrderExploded.OrderLineItems.Sum(oli => oli.ItemUnitPrice) : 0.0M; // Step 2: Build the SMS Msg string payAwayURL = $"{webUrlConfig.HPPBaseUrl}/customerorder/{dbOrderExploded.OrderGuid}"; StringBuilder messageBody = new StringBuilder(); messageBody.AppendLine($"Hello {dbOrderExploded.CustomerName}"); // we do not know what culture the server is set for so we are explicit, we want to make it formats currency with a US $ var specificCulture = System.Globalization.CultureInfo.GetCultureInfo("en-US"); FormattableString formattableString = $"{dbOrderExploded.Merchant.MerchantName} is sending you this link to a secure payment page to enter your payment info for your Order Number: {dbOrderExploded.OrderId:0000} for: {orderTotal:C}"; messageBody.AppendLine(formattableString.ToString(specificCulture)); messageBody.AppendLine($"{payAwayURL}"); // Step 3: Send the SMS msg // convert the phone no to the "normalized format" +15131234567 that the SMS api accepts (bool isValidPhoneNo, string formattedPhoneNo, string normalizedPhoneNo) = Utilities.PhoneNoHelpers.NormalizePhoneNo(dbOrderExploded.PhoneNumber); var msgSid = SMSController.SendSMSMessage(String.Empty, formattedPhoneNo, messageBody.ToString()); // Step 4 Create & Save the SMS event var dbOrderEvent = new OrderEventDBE() { OrderId = dbOrderExploded.OrderId, EventDateTimeUTC = DateTime.UtcNow, OrderStatus = Enums.ORDER_STATUS.SMS_Sent, EventDescription = $"SMS sent to [{normalizedPhoneNo}]." }; await _dbContext.InsertOrderEventAsync(dbOrderEvent); // Step 5: Update the order status dbOrderExploded.Status = Enums.ORDER_STATUS.SMS_Sent; await _dbContext.UpdateOrderAsync(dbOrderExploded); }
/// <summary> /// Inserts new order event /// </summary> /// <param name="newOrderEvent">The new order event.</param> /// <returns>OrderEventDBE</returns> /// <remarks> /// only used by the ResetDB method so we can keep the same guids across reloads. /// </remarks> internal async Task InsertOrderEventAsync(OrderEventDBE newOrderEvent) { this.OrderEvents.Add(newOrderEvent); try { await this.SaveChangesAsync(); } catch (DbUpdateException ex) { // exception was raised by the db (ex: UK violation) var sqlException = ex.InnerException; // we do this to disconnect the exception that bubbles up from the dbcontext which will be disposed when it leaves this method throw new ApplicationException(sqlException.Message); } catch (Exception) { // rethrow exception throw; } }
public async Task <ActionResult> UpdateOrder([FromRoute] Guid orderGuid, [FromBody] NewOrderMBE updatedOrder) { // get the existing order var dbOrder = await _dbContext.GetOrderAsync(orderGuid); // if we did not find a matching order if (dbOrder == null) { return(BadRequest(new ArgumentException($"OrderGuid: [{orderGuid}] not found", nameof(orderGuid)))); } // Biz Logic: Cannot change the order if it has already been paid for. if (dbOrder.Status == Enums.ORDER_STATUS.SMS_Sent || dbOrder.Status == Enums.ORDER_STATUS.Paid) { return(BadRequest(new ArgumentException($"Changes are not allowed after the SMS has been sent or the order has been paid", nameof(orderGuid)))); } #region === Validation ================================================= // validate the input params if (string.IsNullOrEmpty(updatedOrder.CustomerName)) { return(BadRequest(new ArgumentException(@"The order name cannot be blank.", nameof(updatedOrder.CustomerName)))); } else if (string.IsNullOrEmpty(updatedOrder.CustomerPhoneNo)) { return(BadRequest(new ArgumentException(@"The order phone number cannot be blank.", nameof(updatedOrder.CustomerPhoneNo)))); } (bool isValidPhoneNo, string formatedPhoneNo, _) = PhoneNoHelpers.NormalizePhoneNo(updatedOrder.CustomerPhoneNo); if (!isValidPhoneNo) { return(BadRequest(new ArgumentNullException(nameof(updatedOrder.CustomerPhoneNo), $"[{updatedOrder.CustomerPhoneNo}] is NOT a supported Phone No format."))); } else { updatedOrder.CustomerPhoneNo = formatedPhoneNo; } // validate the catalog guids foreach (var orderLineItem in updatedOrder.OrderLineItems) { // try to find the catalog item var catalogItem = await _dbContext.GetCatalogItemAsync(orderLineItem.ItemGuid); // if it did not exist (ie: a invalid guid) if (catalogItem == null) { return(BadRequest(new ArgumentNullException(nameof(orderLineItem.ItemGuid), $"Error : [{orderLineItem.ItemGuid}] Is not a valid catalog item guid."))); } } #endregion try { // Step 1: update the dbOrder with the values we just got dbOrder.CustomerName = updatedOrder.CustomerName; dbOrder.PhoneNumber = updatedOrder.CustomerPhoneNo; dbOrder.Status = Enums.ORDER_STATUS.Updated; await _dbContext.UpdateOrderAsync(dbOrder); // Step 2: in this demo code we are just going to delete and re-add the order line items await _dbContext.DeleteOrderLineItemsAsync(dbOrder.OrderId); //iterate through orderLineItems collection to save it in the db foreach (var orderLineItem in updatedOrder.OrderLineItems) { var catalogItem = await _dbContext.GetCatalogItemAsync(orderLineItem.ItemGuid); var dbOrderLineItem = new OrderLineItemDBE() { ItemName = catalogItem.ItemName, ItemUnitPrice = catalogItem.ItemUnitPrice, OrderId = dbOrder.OrderId, CatalogItemGuid = orderLineItem.ItemGuid }; await _dbContext.InsertOrderLineItemAsync(dbOrderLineItem); } // Step 3: create an event var dbOrderEvent = new OrderEventDBE() { OrderId = dbOrder.OrderId, EventDateTimeUTC = DateTime.UtcNow, OrderStatus = Enums.ORDER_STATUS.Updated, EventDescription = "Order updated." }; //save order event await _dbContext.InsertOrderEventAsync(dbOrderEvent); return(NoContent()); } catch (Exception ex) { return(BadRequest(new ApplicationException($"Error: [{ex.Message}] Failed to update merchant order."))); } }
public async Task <ActionResult> SubmitOrderPayment([FromRoute] Guid orderGuid, [FromBody] PaymentInfoMBE paymentInfo) { //query the db var dbOrderExploded = await _dbContext.GetOrderExplodedAsync(orderGuid); #region === Validation ===================== //Biz Logic: check to see if the order guid is correct if (dbOrderExploded == null) { return(NotFound($"Customer order with ID: {orderGuid} not found")); } // == Expiration Date ===================================== // Step 2a: Is it even a valid date (this takes care of wacky month values) if (!DateTime.TryParse($"{paymentInfo.ExpMonth}/1/{ paymentInfo.ExpYear}", out DateTime parsedDate)) { return(BadRequest($"{paymentInfo.ExpMonth}/{paymentInfo.ExpYear} is not a valid expiration date")); } // Step 2b: The expiration date cannot be to far into the future (this takes care of yrs too far into the future) if (parsedDate > DateTime.Today.AddYears(5)) { return(BadRequest($"{paymentInfo.ExpMonth}/{ paymentInfo.ExpYear} is not a valid expiration date")); } // Step 2c: Is the card still valid today (cards are valid thru the last day of the month (this check prevents dates in the past) DateTime calcExpireDate = parsedDate.AddMonths(1).AddDays(-1); if (DateTime.Today > calcExpireDate) { return(BadRequest($"Payment Instrument is no longer valid, expired on {calcExpireDate:MM/dd/yyyy}")); } // == Tip Amount ==================================== // Step 3a: Check to see if the order has a tip even when the merchant doesn't support tips. if (dbOrderExploded.Merchant.IsSupportsTips == false && paymentInfo.TipAmount.HasValue && paymentInfo.TipAmount.Value != 0.0M) { return(BadRequest($"This merchant does NOT support tips.")); } // Step 3b: Biz Logic: check to see if tip is less than zero if (paymentInfo.TipAmount.HasValue && paymentInfo.TipAmount.Value < 0.0M) { return(BadRequest($"The tip amount: {paymentInfo.TipAmount} cannot less than $0.00.")); } // == CC Number =================================== string paymentPan = paymentInfo.PAN.Trim().CleanUp(); // // Step 4a: Biz Logi cc: check to see if credit card number is fine. if (paymentPan.Length != 16) { return(BadRequest("Your Credit card number must be 16 digits.")); } if (!paymentPan.CheckLuhn()) { return(BadRequest("Your Credit card number must pass a LUN check.")); } // == Auth Code =================================== // check to see if auth code is present if (!String.IsNullOrEmpty(dbOrderExploded.AuthCode)) { return(BadRequest("Order has already been marked paid.")); } #endregion try { //update the dbOrder with the values we just got dbOrderExploded.CreditCardNumber = paymentPan; dbOrderExploded.ExpMonth = paymentInfo.ExpMonth; dbOrderExploded.ExpYear = paymentInfo.ExpYear; dbOrderExploded.AuthCode = CardNetworkHelper.GenerateAuthCode(); dbOrderExploded.TipAmount = paymentInfo.TipAmount ?? 0.9M; //update order dbOrderExploded.Status = Enums.ORDER_STATUS.Paid; await _dbContext.UpdateOrderAsync(dbOrderExploded); //Write the order payment event var dbOrderEvent = new OrderEventDBE() { OrderId = dbOrderExploded.OrderId, EventDateTimeUTC = DateTime.UtcNow, OrderStatus = Enums.ORDER_STATUS.Paid, EventDescription = $"Order has been paid." }; //save order event await _dbContext.InsertOrderEventAsync(dbOrderEvent); // send notification to all connected clients await _messageHub.Clients.All.SendAsync("ReceiveMessage", "Server", $"Order: [{orderGuid}] updated"); return(NoContent()); } catch (Exception ex) { return(BadRequest(new ApplicationException($"Error: [{ex.Message}] Failed to send order payment."))); } }