Receiving IPN and SSL Secure Channel - c#

I have a project in MVC. Using IPN to receive notifications of payments made to update the database. The project works fine until I click "Return to ___ Test Store".
I receive the following server error:
Controller: I followed this example => PayPal & MVC
if (sAmountPaid != null)
{
// look up the requested service using the item_number
int id = int.Parse(Request["item_number"]);
var requestedService = db.RequestedServices.Where(rs => rs.RequestedServiceId == id).SingleOrDefault();
// look up the payment adjustment
var paymentAdjustment = requestedService.PaymentAdjustments.FirstOrDefault();
decimal amount = decimal.Parse(Request["mc_gross"]); // pull out the total amount
string payerEmail = Request["payer_email"]; // and the paypal user email
// before creating a new payment object, make sure there isn't an item with the transaction id already
// in the database
var paymentCheck = db.Payments.Where(p => p.TransactionId == transactionID).SingleOrDefault();
if (paymentCheck != null)
{
ViewBag.Success = "yes";
return View();
}
// if a payment with that id hasn't been made, create one
Payment payment = new Payment()
{
DateSubmitted = DateTime.Now.AddHours(2),
IsActive = true,
IsCompleted = false,
RequestedServiceId = id,
TransactionId = transactionID,
Amount = amount,
Email = payerEmail,
};
// set the payment completion status based on the result
if (Request["payment_status"].ToLower() == "completed")
{
payment.IsCompleted = true;
paymentAdjustment.IsCompleted = true;
}
db.Payments.Add(payment);
db.Entry(paymentAdjustment).State = EntityState.Modified;
db.SaveChanges();
// send an email notifying the contractor that a new service has been requested
IEmailSender emailsender = new GoDaddyEmailSender(); // send the emails via GoDaddy (implementing IEmailSender interface)
string subject = "Full Payment Adjustment Completed!";
string body = null;
var customer = db.my_aspnet_users.Where(u => u.id == requestedService.UserId).SingleOrDefault();
var yardladEmail = db.SiteSettings.Where(ss => ss.Name == "Yard Lad Contact Email").Single().Value;
foreach (var user in requestedService.Contractor.ContractorUsers)
{
var recipientEmail = Membership.GetUser(user.UserId, false).Email;
var userProfile = db.UserProfiles.Where(up => up.UserId == user.UserId).SingleOrDefault();
body += "<p>A new payment amount for the " + requestedService.ContractorService.Service.Name.ToLower() + " service has been completed!</p>";
body += "<p>Login to your account here to view the changes</p>";
// send email to the users (will need to replace the emails when deploying)
try
{
emailsender.SendEmail(subject, body, from: yardladEmail, recipient: recipientEmail);
}
catch (Exception ex)
{
// log
}
}
ViewBag.Success = "yes";
return View();
}
I have tried running the debugger, but when it reaches this portion of my controller nothing is triggered. All other steps and processes before work with no problems.

Related

How to check if payment was successful or not Stripe ASP.NET

public ActionResult Index()
{
StripeConfiguration.ApiKey = "secretKey";
var options = new SessionCreateOptions
{
PaymentMethodTypes = new List<string> {
"card",
},
LineItems = new List<SessionLineItemOptions> {
new SessionLineItemOptions {
Name = "Basic Plan",
Description = "Up to 1 Featured Product",
Amount = 1000,
Currency = "usd",
Quantity = 1,
},
},
SuccessUrl = "https://localhost:44390/payment/successful",
CancelUrl = "https://localhost:44390/payment/fail",
PaymentIntentData = new SessionPaymentIntentDataOptions
{
Metadata = new Dictionary<string, string>
{
{"Order_ID", "123456" },
{"Description", "Basic Plan" }
}
}
};
var service = new SessionService();
Session session = service.Create(options);
return PartialView("_PaymentIndex", session);
}
Is there any way of checking the status of the payment, because I wanna call another action if the payment is confirmed. Like the same as adding a successful url, but for an action. I'm kinda new to this so :/
Edit:
I added a webhook to https://xxxx.eu.ngrok.io/payment/UpdatePaymentStatus for this action:
[HttpPost]
public ActionResult UpdatePaymentStatus()
{
try
{
StripeConfiguration.ApiKey = "key";
Stream req = Request.InputStream;
req.Seek(0, System.IO.SeekOrigin.Begin);
string json = new StreamReader(req).ReadToEnd();
myLogger.GetInstance().Warning(User.Identity.Name, "| Check |", "Payment/UpdatePaymentStatus");
// Get all Stripe events.
var stripeEvent = EventUtility.ParseEvent(json);
string stripeJson = stripeEvent.Data.RawObject + string.Empty;
var childData = Charge.FromJson(stripeJson);
var metadata = childData.Metadata;
int orderID = -1;
string strOrderID = string.Empty;
if (metadata.TryGetValue("Order_ID", out strOrderID))
{
int.TryParse(strOrderID, out orderID);
// Find your order from database.
// For example:
// Order order = db.Order.FirstOrDefault(x=>x.ID == orderID);
}
switch (stripeEvent.Type)
{
case Events.ChargeCaptured:
case Events.ChargeFailed:
case Events.ChargePending:
case Events.ChargeRefunded:
case Events.ChargeSucceeded:
case Events.ChargeUpdated:
var charge = Charge.FromJson(stripeJson);
string amountBuyer = ((double)childData.Amount / 100.0).ToString();
if (childData.BalanceTransactionId != null)
{
long transactionAmount = 0;
long transactionFee = 0;
if (childData.BalanceTransactionId != null)
{
// Get transaction fee.
var balanceService = new BalanceTransactionService();
BalanceTransaction transaction = balanceService.Get(childData.BalanceTransactionId);
transactionAmount = transaction.Amount;
transactionFee = transaction.Fee;
}
// My status, it is defined in my system.
int status = 0; // Wait
double transactionRefund = 0;
// Set order status.
if (stripeEvent.Type == Events.ChargeFailed)
{
status = -1; // Failed
myLogger.GetInstance().Warning(User.Identity.Name, "| Purchase of basic plan failed |", "Payment/UpdatePaymentStatus");
}
if (stripeEvent.Type == Events.ChargePending)
{
status = -2; // Pending
myLogger.GetInstance().Warning(User.Identity.Name, "| Purchase of basic plan pending |", "Payment/UpdatePaymentStatus");
}
if (stripeEvent.Type == Events.ChargeRefunded)
{
status = -3; // Refunded
transactionRefund = ((double)childData.AmountRefunded / 100.0);
myLogger.GetInstance().Warning(User.Identity.Name, "| Purchase of basic plan refunded |", "Payment/UpdatePaymentStatus");
}
if (stripeEvent.Type == Events.ChargeSucceeded)
{
status = 1; // Completed
myLogger.GetInstance().Info(User.Identity.Name, "Bought Basic Plan", "Payment/UpdatePaymentStatus");
}
// Update data
// For example: database
// order.Status = status;
// db.SaveChanges();
}
break;
default:
//log.Warn("");
break;
}
return Json(new
{
Code = -1,
Message = "Update failed."
});
}
catch (Exception e)
{
//log.Error("UpdatePaymentStatus: " + e.Message);
return Json(new
{
Code = -100,
Message = "Error."
});
}
}
public ActionResult Successful()
{
return View();
}
public ActionResult Fail()
{
return View();
}
}
Still not working though. I'm not getting any logs(using nLog) and the webhook is failing(in stripe dashboard).
Your code is creating a Checkout Session via the API. This is associated with Stripe's Checkout product. Stripe controls the UI to collect all the payment method details which means you're going to redirect your customer directly to Stripe's hosted page.
Once a customer pays, they will be redirected to your payment page configured in SuccessUrl in your code. You need to write code that detects the customer hitting that URL and map it to the session they were paying.
Additionally, it's possible for a customer to close their browser too before hitting your page. For that reason, Stripe also sends the checkout.session.completed event to your webhook handler. You can write code to handle that event and look at the status of their payment and all relevant information.
Stripe covers all possible fulfillment approaches in their documentation: https://stripe.com/docs/payments/checkout/fulfill-orders

Data synchronization (validation) between two clients

I have a webshop built on ASP.NET Boilerplate (Angular frontend and MSSQL database).
The webshop contains items and I want to keep an inventory of these items.
Every time an order is created the inventory is updated. So basically I have a database with Webshops, Items and Orders.
I have repositories and managers for these objects.
All works fine, but the issue occurs when two clients at the same time load the webshop.
Client1 opens webpage:
Webshop1
Item1: "10 items available"
Item2: "8 items available"
Client2 opens webpage at the same time:
Webshop1
Item1: "10 items available"
Item2: "8 items available"
The first one that buys all the available items, should be able to create an order and the second one should get an error.
When an order is created, the backend checks if there are enough items available.
But when the webshop is loaded BEFORE the order of the first client is created, the second client does not know the updated inventory and will be able to create the order as well.
Meaning 20 items of Item1 can be sold!
How do I "sync" the data between the two sessions in the backend? It seems somehow the data is cached in the backend when loading the webshop.
CreateOrder function
public async Task<CreateOrderResponseDto> Create(CreateOrderDto input, long? userId)
{
input.OrderItems.ForEach(async o =>
{
if (!(await _salesItemManager.ReserveStock(o.SalesItemId, o.Quantity)).IsSuccess)
{
throw CodeException.ToAbpValidationException("OrderItem", "OrderItemCreate");
}
});
var salesPage = await _salesPageManager.Get(input.SalesPageId, false);
if (salesPage.GetState() != StatePage.Published)
{
throw CodeException.ToAbpValidationException("Order", "PageNotAvailable");
}
if (salesPage.CommentsRequired.HasValue && salesPage.CommentsRequired.Value)
{
if (string.IsNullOrWhiteSpace(input.Description))
{
throw CodeException.ToAbpValidationException("Order", "CommentsRequired");
}
}
var order = new Order
{
Address = input.Address,
City = input.City,
LastName = input.LastName,
Name = input.Name,
PostalCode = input.PostalCode,
Email = input.Email,
PhoneNumber = input.PhoneNumber,
Description = input.Description,
SalesPage = salesPage
};
try
{
order.Price = await _salesItemManager.GetPriceByOrders(input.OrderItems);
order = await _orderRepository.InsertAsync(order);
input.OrderItems.ForEach(async o =>
{
var orderItem = new OrderItem();
orderItem.SalesItemId = o.SalesItemId;
orderItem.OrderId = order.Id;
orderItem.Quantity = o.Quantity;
await _orderItemRepository.InsertAsync(orderItem);
});
if (input.SelectedSalesPageOptionId.HasValue)
{
order.SalesOption = await _salesPageManager.GetOption(input.SelectedSalesPageOptionId.Value);
}
}
catch (Exception e)
{
throw CodeException.ToAbpValidationException("OrderItem", "OrderItemCreate");
}
if (userId.HasValue && salesPage.User.Id == userId.Value)
{
var payment = await _paymentManager.CreateManualPayment(order, input.IsPaid);
order.Payment = payment;
return new CreateOrderResponseDto() { IsSuccess = true, PaymentUrl = string.Empty, OrderId = order.Id.ToString() };
}
else
{
var payment = await _paymentManager.CreatePayment(order);
order.Payment = payment;
return new CreateOrderResponseDto() { IsSuccess = true, PaymentUrl = payment.PaymentUrl, OrderId = order.Id.ToString() };
}
}
ReserveStock function
public async Task<GeneralDto> ReserveStock(Guid itemId, int quantity)
{
var salesItem = await _salesItemRepository.GetAsync(itemId);
if (salesItem == null || salesItem.Stock == null || salesItem.ReservedStock == null)
return new GeneralDto() { IsSuccess = false };
if (salesItem.Stock < quantity)
{
return new GeneralDto() { IsSuccess = false };
}
salesItem.Stock -= quantity;
salesItem.ReservedStock += quantity;
try
{
await _salesItemRepository.UpdateAsync(salesItem);
}
catch (Exception e)
{
throw CodeException.ToAbpValidationException("SalesItem", "SalesItemUpdate");
}
return new GeneralDto() { IsSuccess = true };
}
The problem is not that the data is cached.
The problem is that your ReserveStock checks are async tasks that are not awaited by the caller:
input.OrderItems.ForEach(async o =>
{
if (!(await _salesItemManager.ReserveStock(o.SalesItemId, o.Quantity)).IsSuccess)
{
throw CodeException.ToAbpValidationException("OrderItem", "OrderItemCreate");
}
});
Assign your async ReserveStock checks to an array of tasks that is awaited by the caller:
var reserveStockChecks = input.OrderItems.Select(async o =>
{
if (!(await _salesItemManager.ReserveStock(o.SalesItemId, o.Quantity)).IsSuccess)
{
throw CodeException.ToAbpValidationException("OrderItem", "OrderItemCreate");
}
}).ToArray();
await Task.WhenAll(reserveStockChecks);

ASP.NET 2.2 Signalr Core Getting the connection Ids of all participants within a specific room

I'd like to know how to get all the connectionIds of all participants within a specific chat room. Currently, I am able to store the details of participants in a chat room. However, since I am unable to manually set the connectionIds, how can I ensure that the next time they rejoin a room, that the messages are delivered to them?
Also, what is the purpose of a group? And how do I use it?
ChatHub.cs
[Authorize]
public class ChatHub : Hub
{
private readonly static ConnectionMapping<string> _connections = new ConnectionMapping<string>();
private ChatSessionData chatSessionData;
private ChatParticipantData chatParticipantData;
private ChatMessageData chatMessageData;
private ChatConnectionData chatConnectionData;
public ChatHub(ChatSessionData chatSessionData, ChatConnectionData chatConnectionData, ChatParticipantData chatParticipantData, ChatMessageData chatMessageData)
{
this.chatSessionData = chatSessionData;
this.chatParticipantData = chatParticipantData;
this.chatMessageData = chatMessageData;
this.chatConnectionData = chatConnectionData;
}
public async Task SendMessage(string user, string message)
{
var httpContext = Context.GetHttpContext();
var SessionId = httpContext.Request.Query["SessionId"];
var UserId = Context.User.Claims.Where(c => c.Type == "ChatSampleId").Select(c => c.Value).SingleOrDefault();
var users = chatConnectionData.GetBySessionId(SessionId);
List<string> connectionIds = new List<string>();
if (users.Count > 0)
{
foreach (var item in users)
{
connectionIds.Add(item.ConnectionId);
}
CreateChatMessageViewModel ccmvm = new CreateChatMessageViewModel
{
Id = Guid.NewGuid().ToString(),
UserId = UserId,
SessionId = SessionId,
Name = user,
Message = message,
CreatedOn = DateTime.Now
};
chatMessageData.Save(ccmvm);
//await Clients.All.SendAsync("ReceiveMessage",user, message);
await Clients.Clients(connectionIds).SendAsync("ReceiveMessage", user, message);
}
}
public async Task SessionNotification(string user, string message)
{
var httpContext = Context.GetHttpContext();
var SessionId = httpContext.Request.Query["SessionId"];
var UserId = Context.User.Claims.Where(c => c.Type == "ChatSampleId").Select(c => c.Value).SingleOrDefault();
var users = chatConnectionData.GetBySessionId(SessionId);
List<string> connectionIds = new List<string>();
if (users.Count > 0)
{
foreach (var item in users)
{
connectionIds.Add(item.ConnectionId);
}
connectionIds.Add(Context.ConnectionId);
}
else
{
connectionIds.Add(Context.ConnectionId);
}
//if only have one connectionid, send the message anyway
await Clients.Clients(connectionIds).SendAsync("ReceiveMessage", user, message);
}
public override Task OnConnectedAsync()
{
var httpContext = Context.GetHttpContext();
var SessionId = httpContext.Request.Query["SessionId"];
var UserName = Context.User.Claims.Where(c => c.Type == "UserName").Select(c => c.Value).SingleOrDefault();
var UserId = Context.User.Claims.Where(c => c.Type == "ChatSampleId").Select(c => c.Value).SingleOrDefault();
var chatSession = chatParticipantData.GetBySessionIdAndUserId(SessionId, UserId);
if (chatSession == null)
{
//New Connection
CreateChatParticipantViewModel ccpvm = new CreateChatParticipantViewModel
{
Id = Guid.NewGuid().ToString(),
SessionId = SessionId,
UserId = UserId
};
chatParticipantData.Save(ccpvm);
CreateChatMessageViewModel ccmvm = new CreateChatMessageViewModel
{
Id = Guid.NewGuid().ToString(),
UserId = UserId,
SessionId = SessionId,
Name = UserName,
Message = "has joined the conversation",
CreatedOn = DateTime.Now
};
chatMessageData.Save(ccmvm);
SessionNotification(UserName, "has joined the conversation");
CreateChatConnectionViewModel cccvm = new CreateChatConnectionViewModel
{
Id = Guid.NewGuid().ToString(),
ConnectionId = Context.ConnectionId,
UserAgent = httpContext.Request.Headers["User-Agent"],
Connected = true,
SessionId = SessionId,
UserId = UserId,
CreatedOn = DateTime.Now
};
chatConnectionData.Save(cccvm);
Groups.AddToGroupAsync(cccvm.ConnectionId, UserName);
}
else
{
var connectionDetails = chatConnectionData.GetBySessionIdAndUserId(SessionId, UserId);
if (connectionDetails != null)
{
//save the connectionId or Group details to the database and reload it
Groups.AddToGroupAsync(connectionDetails.ConnectionId, UserName);
}
}
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
return base.OnDisconnectedAsync(exception);
}
}
A short answer to your question is... "what is the purpose of a group? And how do I use it?"
Groups in SignalR provide a method for broadcasting messages to specified subsets of connected clients. A group can have any number of clients, and a client can be a member of any number of groups
For details about the group please visit this official link
Note: In your case a group can be used to represent a chat room.
Answer to your second question... "how can I ensure that the next time they rejoin a room, that the messages are delivered to them?"
I believe you need maintain/persist the chat history of a room in persistent storage like in database or maybe within your ChatHub(it depends on your business domain). So that every time a new user joins or rejoins a room he/she can see all previous messages within that room. It would be the responsibility of ChatHub to send chat history to each new joining user.
Answer to question: " But how do I ensure that the person joining the room will get the new messages?"
Whenever a person connects to the chatHub you need to store his connectionId against the room like:
Groups.Add(Context.ConnectionId, "Your Chat Room Name");
Once the new user is added to the group, the next time you broadcast the message in a group the newly joined user will also get the message. Like so:
Clients.Group("Your chat room name").SendAsync("ReceiveMessage", user, message);
Hope this helps.

Bug in order program in which payment can be bypassed

This project makes the customer first create an order and them has to pay for said order via Braintree, However the issue that I am getting is that a customer can create an order and them close the application. This will cause the order to still exist however the customer did not have to pay for their order. If any one knows of a work around for this their help would be thanked. (The orders and payments work. Its just this bug that I'm worried about)
Orders Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> FirstClassCreate(FormCollection values)
{
var order = new Order();
TryUpdateModel(order);
var customer = db.Users.FirstOrDefault(x => x.Email == User.Identity.Name);
var cart = ShoppingCart.GetCart(this.HttpContext);
try
{
Sets the order attributes
order.DeliveryDate = DateTime.Now.AddDays(1);
order.DeliveryMethod = "First Class";
order.FirstName = customer.FirstName;
order.LastName = customer.LastName;
order.PostalCode = customer.PostalCode;
order.State = customer.State;
order.City = customer.City;
order.Email = customer.Email;
order.Country = customer.Country;
order.Phone = customer.PhoneNumber;
order.Address = customer.Address;
order.Username = customer.Email;
order.OrderDate = DateTime.Now;
var currentUserId = User.Identity.GetUserId();
order.Total = cart.GetFirstClass();
if (order.SaveInfo && !order.Username.Equals("guest#guest.com"))
{
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
var ctx = store.Context;
var currentUser = manager.FindById(User.Identity.GetUserId());
//Save this back
//http://stackoverflow.com/questions/20444022/updating-user-data-asp-net-identity
//var result = await UserManager.UpdateAsync(currentUser);
await ctx.SaveChangesAsync();
await storeDB.SaveChangesAsync();
}
Saves the order to the database
//Save Order
storeDB.Orders.Add(order);
await storeDB.SaveChangesAsync();
//Process the order
cart = ShoppingCart.GetCart(this.HttpContext);
order.Total = cart.GetFirstClass();
order = cart.CreateOrder(order);
return RedirectToAction("FirstClass", "Checkouts");
}
catch
{
//Invalid - redisplay with errors
return View(order);
}
}
Checkouts controller
public ActionResult CreateFirstClass(FormCollection collection)
{
var gateway = config.GetGateway();
Decimal amount;
//Need to get the amount
try
{
amount = Convert.ToDecimal(Request["amount"]);
}
catch (FormatException e)
{
TempData["Flash"] = "Error: 81503: Amount is an invalid format.";
return RedirectToAction("New");
}
string nonceFromTheClient = collection["payment_method_nonce"];
var cart = ShoppingCart.GetCart(this.HttpContext);
//if (id == null)
//{
// return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
//}
//order = Orders.FindAsync(id);
Gets the necessary payment methods
var request = new TransactionRequest
{
Amount = cart.GetFirstClass(),
PaymentMethodNonce = nonceFromTheClient,
Options = new TransactionOptionsRequest
{
SubmitForSettlement = true
}
};
cart.EmptyCart();
Result<Transaction> result = gateway.Transaction.Sale(request);
if (result.IsSuccess())
{
Transaction transaction = result.Target;
return RedirectToAction("Show", new { id = transaction.Id });
}
else if (result.Transaction != null)
{
return RedirectToAction("Show", new { id = result.Transaction.Id });
}
else
{
string errorMessages = "";
foreach (ValidationError error in result.Errors.DeepAll())
{
errorMessages += "Error: " + (int)error.Code + " - " + error.Message + "\n";
}
TempData["Flash"] = errorMessages;
return RedirectToAction("New");
}
}
#

How do invoke the Paypal IPN using the REST API c# .net

I am having problems invoking the PayPal IPN. I dont know which URL to give or which URL i am meant to give. I have looked all over the internet for help but there does not seem to be anything available hence why i have come here.
So firstly, i have the PaymentWithPaypal Action
public ActionResult PaymentWithPaypal(int? id, Page page)
{
//getting the apiContext as earlier
APIContext apiContext = Models.Configuration.GetAPIContext();
try
{
string payerId = Request.Params["PayerID"];
if (string.IsNullOrEmpty(payerId))
{
string baseURI = Request.Url.Scheme + "://" + Request.Url.Authority + "/ControllerName/PaymentWithPayPal?";
var guid = Guid.NewGuid().ToString();
//CreatePayment function gives us the payment approval url
//on which payer is redirected for paypal acccount payment
var createdPayment = this.CreatePayment(apiContext, baseURI + "guid=" + guid);
//get links returned from paypal in response to Create function call
var links = createdPayment.links.GetEnumerator();
string paypalRedirectUrl = null;
while (links.MoveNext())
{
Links lnk = links.Current;
if (lnk.rel.ToLower().Trim().Equals("approval_url"))
{
//saving the payapalredirect URL to which user will be redirected for payment
paypalRedirectUrl = lnk.href;
}
}
// saving the paymentID in the key guid
Session.Add(guid, createdPayment.id);
return Redirect(paypalRedirectUrl);
}
else
{
// This section is executed when we have received all the payments parameters
// from the previous call to the function Create
// Executing a payment
var guid = Request.Params["guid"];
var executedPayment = ExecutePayment(apiContext, payerId, Session[guid] as string);
if (executedPayment.state.ToLower() != "approved")
{
return View("FailureView");
}
}
}
catch (Exception ex)
{
Logger.Log("Error" + ex.Message);
return View("FailureView");
}
return View("SuccessView");
}
This is the code for the IPN.
[HttpPost]
public HttpStatusCodeResult Receive()
{
//Store the IPN received from PayPal
LogRequest(Request);
//Fire and forget verification task
Task.Run(() => VerifyTask(Request));
//Reply back a 200 code
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
private void VerifyTask(HttpRequestBase ipnRequest)
{
var verificationResponse = string.Empty;
try
{
var verificationRequest = (HttpWebRequest)WebRequest.Create("https://www.sandbox.paypal.com/cgi-bin/webscr");
//Set values for the verification request
verificationRequest.Method = "POST";
verificationRequest.ContentType = "application/x-www-form-urlencoded";
var param = Request.BinaryRead(ipnRequest.ContentLength);
var strRequest = Encoding.ASCII.GetString(param);
//Add cmd=_notify-validate to the payload
strRequest = "cmd=_notify-validate&" + strRequest;
verificationRequest.ContentLength = strRequest.Length;
//Attach payload to the verification request
var streamOut = new StreamWriter(verificationRequest.GetRequestStream(), Encoding.ASCII);
streamOut.Write(strRequest);
streamOut.Close();
//Send the request to PayPal and get the response
var streamIn = new StreamReader(verificationRequest.GetResponse().GetResponseStream());
verificationResponse = streamIn.ReadToEnd();
streamIn.Close();
}
catch (Exception exception)
{
Logger.Log("Error" + exception.Message);
//Capture exception for manual investigation
}
ProcessVerificationResponse(verificationResponse);
}
private void LogRequest(HttpRequestBase request)
{
// Persist the request values into a database or temporary data store
}
private void ProcessVerificationResponse(string verificationResponse)
{
if (verificationResponse.Equals("VERIFIED"))
{
Logger.Log("Verified");
// check that Payment_status=Completed
// check that Txn_id has not been previously processed
// check that Receiver_email is your Primary PayPal email
// check that Payment_amount/Payment_currency are correct
// process payment
}
else if (verificationResponse.Equals("INVALID"))
{
Logger.Log(verificationResponse);
}
else
{
//Log error
}
}
Now to clear things up. My understanding of the IPN is that when a customer purchases an item, the SELLER will get an email telling them that they have sold a product and then from this you can access transactionId etc.
So in my view i have a form with a button that looks like this.
#Html.ActionLink("Buy Now", "PaymentWithPaypal", new { Id = Model.Id, #class = "" })
This is what takes the customer to paypal where they can then purchase but this is where i am stuck because im not sure how to call the IPN or if it needs its own view.
ANY CLARITY WOULD BE OF MUCH HELP AT THIS MOMENT IN TIME.
One way is to put this under PayPal account settings. Once you click on your "App", below it you see the redirect url option. Just add it there. Paypal .net sdk doesn't have the option to pass notify_url. All other modes have. Because, paypal.net sdk accepts return_url which is usually the same action method as also mentioned in your code.
Check this:
https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNSetup/#
In case you want to achieve real time events, you need to use webhooks now. Documentation below:
https://github.com/paypal/PayPal-NET-SDK/wiki/Webhook-Event-Validation

Categories