First, let me say that I have been at this for far too long, and the parts where I am stuck with, I believe should be the simplest of tasks to accomplish. Yet, I am unable to do them. I am really confused.
I am integrating PayPal into my website. I've developed many websites before, but this is my first time at doing payments.
I have the following code (unmodified - which I copied and pasted [don't panic! I did this for a reason]):
// ASP .NET C#
using System;
using System.IO;
using System.Text;
using System.Net;
using System.Web;
public partial class csIPNexample : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//Post back to either sandbox or live
string strSandbox = "https://www.sandbox.paypal.com/cgi-bin/webscr";
string strLive = "https://www.paypal.com/cgi-bin/webscr";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(strSandbox);
//Set values for the request back
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
byte[] param = Request.BinaryRead(HttpContext.Current.Request.ContentLength);
string strRequest = Encoding.ASCII.GetString(param);
strRequest += "&cmd=_notify-validate";
req.ContentLength = strRequest.Length;
//for proxy
//WebProxy proxy = new WebProxy(new Uri("http://url:port#"));
//req.Proxy = proxy;
//Send the request to PayPal and get the response
StreamWriter streamOut = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII);
streamOut.Write(strRequest);
streamOut.Close();
StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream());
string strResponse = streamIn.ReadToEnd();
streamIn.Close();
if (strResponse == "VERIFIED")
{
//check the payment_status is 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 (strResponse == "INVALID")
{
//log for manual investigation
}
else
{
//log response/ipn data for manual investigation
}
}
}
Now, I know what this code means, and I know what it does. The only part I am having difficulty with, is this part:
if (strResponse == "VERIFIED")
{
//check the payment_status is 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 (strResponse == "INVALID")
{
//log for manual investigation
}
else
{
//log response/ipn data for manual investigation
}
Which, you're probably laughing about right now. That's ok:
//check the payment_status is Completed
I think that line should be done like this:
#{
if(Request.QueryString["payment_status"] == "Completed")
{
// Payment status is completed.
}
}
And, I think I should do pretty much the same thing (getting the Request["etc"] variables and comparing their values) for the rest of the commented lines, and also matching the data with data inside the DB which relates to the user.
Can somebody please help me out? I really have looked everywhere, and it seems like all of the code samples online that I could find, never show you the code for this part.
Your help will be really appreciated.
Thank you
Your line will be like this, its replay with a post! the content is the VERIFIED, but the values are on post, not on url.
if (strResponse == "VERIFIED")
{
// Now All informations are on
HttpContext.Current.Request.Form;
// for example you get the invoice like this
HttpContext.Current.Request.Form["invoice"]
// or the payment_status like
HttpContext.Current.Request.Form["payment_status"]
}
else
{
//log for manual investigation
}
Now after the Response is Verified and the Payment_status is Completed, you have extra options on txn_type, and you can check if the email are correct, if the amount are correct etc. So your code as you ask it will be like:
#{
if(HttpContext.Current.Request.Form["payment_status"] == "Completed")
{
// Payment status is completed.
}
}
You can check if payment_status == "Completed" that means the order is completed but you also need to check if the amount is correct, the email is correct, and the other reasons are correct (like pending, echeck, hold)
Related
EDIT
This question was for a workaround. Getting a successful login by using HttpWebRequests. Not on how to use the api.
Question
I noticed in the API there was no way to get a usernames password.
This is what I have now as a test. I thought I could just get the "Wrong user/pass" response first and go from there. All I get is the pages source code.
Anyone have any pointers or advice?
I am definitively logging in. In Account Admin and Login History, it shows me logging in. But the server is not serving any useful response text for the login. And now, I locked myself out using wrong passwords to sort through the streamreader lol.
public string DoVerification(string email, string password)
{
var request = (HttpWebRequest)WebRequest.Create("https://app.smartsheet.com/b/home");
var postData = "loginEmail=" + email;
postData += "&loginPassword=" + password;
postData += "&action=login";
var data = Encoding.ASCII.GetBytes(postData);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
var response = (HttpWebResponse)request.GetResponse();
return new StreamReader(response.GetResponseStream()).ReadToEnd();
}
private void btnLogin_Click(object sender, EventArgs e)
{
string response = DoVerification("test#test.com", "12345");
MessageBox.Show(response.ToString());
}
It's unclear what you are trying to achieve or why you expect it to work.
There is no way to retrieve a password through the API. That would be a bad idea.
You aren't actually using the API. API endpoints start with https://api.smartsheet.com/2.0 and are documented here: http://smartsheet-platform.github.io/api-docs/
After comparing both sources from the returned response.
I have these two js functions.
Failed attempt
function loggedFailures() {
logExternalGTMEvent({'event': 'app-login-failure','method': 'onsite','error': 'AUTH_NO_MATCHING_USER'}); return true
}
Successful attempt
function loggedFailures() {
return false
}
I just simply check for one or the other.
And for the record, putting the users password in the api is not a bad idea.... Smartsheets lets us delete any user through the API, so I don't see what it would matter.
I'm trying to get IPN working for my site but until now without any success.
I've red all PayPals documentation and i've setted up all the requires configurations to get IPN working.
Here's what i have done so far:
1. Created a dev account;
2. Updated my Website Payment Preferences to Auto Return: On;
3. Turned on Instant payment notifications in my profile properties;
4. Created a test payment option event:
protected void btCheckout_Click(object sender, EventArgs e)
{
var queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);
queryString["cmd"] = "_cart";
queryString["business"] = "xpto-facilitator#xpto.com";
queryString["upload"] = "1";
queryString["item_name_1"] = "My Cart Item 1";
queryString["quantity_1"] = "1";
queryString["amount_1"] = "100.00";
queryString["shopping_url"] = "http://xpto.com/Client/Checkout";
queryString["return"] = "http://xpto.com/Client/Checkout";
queryString["notify_url"] = "http://xpto.com/CheckoutResult.aspx";
Response.Redirect("https://www.sandbox.paypal.com/cgi-bin/webscr?" + queryString.ToString());
}
5. Created a IPN page:
protected void Page_Load(object sender, EventArgs e)
{
//Post back to either sandbox or live
string strSandbox = "https://www.sandbox.paypal.com/cgi-bin/webscr";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(strSandbox);
//Set values for the request back
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
byte[] param = Request.BinaryRead(HttpContext.Current.Request.ContentLength);
string strRequest = Encoding.ASCII.GetString(param);
strRequest += "&cmd=_notify-validate";
req.ContentLength = strRequest.Length;
//Send the request to PayPal and get the response
StreamWriter streamOut = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII);
streamOut.Write(strRequest);
streamOut.Close();
StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream());
string strResponse = streamIn.ReadToEnd();
streamIn.Close();
if (strResponse == "VERIFIED")
{
//log for debugging purposes
LogManager.Error("CheckoutResult VERIFIED:" + strResponse);
}
else if (strResponse == "INVALID")
{
//log for debugging purposes
LogManager.Error("CheckoutResult INVALID: " + strResponse);
}
else
{
//log for debugging purposes
LogManager.Error("CheckoutResult something else: " + strResponse);
}
}
I manage to proceed with the checkout and successfully paid the items;
My IPN page wasn't executed because i don't have anything logged;
Tested my handler with the Instant Payment Notification (IPN) simulator and i always get the following error:
We could not send an IPN due to an HTTP error: 401: Unauthorized
What am i doing wrong? Am i missing anything?
The notify_url you're specifying for IPN is set to an endpoint that requires a login.
queryString["notify_url"] = "http://xpto.com/CheckoutResult.aspx";
This URL must be set to an endpoint that can accept and process POST requests from the IPN service without any login being required (I can't seem to find any documentation that specifically mentions this requirement). This is the primary reason why the IPN received by your listener must then be verified (which you look to be doing correctly) because prior to verification there's no way to fully trust the received request was sent by PayPal.
I faced with the issue that after retrying the request my POST data got lost somehow.
Code sample below. (Please note that request.timeout = 1 set for testing purposes to reproduce the behavior shown in the code below):
//post_data_final getting
private void request_3()
{
for(int i=1; i<=5; i++)
{
byte[] byteArray = Encoding.ASCII.GetBytes(post_data_final);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(site_URI);
request.Method = "POST";
//some headers info
request.Timeout = 1;
request.ContentLength = byteArray.Length;
using (Stream os = request.GetRequestStream())
{
os.Write(byteArray, 0, byteArray.Length);
}
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
//some code about response
}
catch (WebException wex)
{
if (wex.Status == WebExceptionStatus.Timeout)
{
continue;
}
//some additional checks
}
}
}
The magic is that first request (until Request timeout error) goes well. Further requests are going without POST data, but content length is counted properly (i.e. stays the same as in previous request).
Updated:
post_data_final getting is separate function. It is not used (except byteArray) or changed in request_3() function.
Request works fine if it got into for loop and Timeout exception has not occured. So if I just put my request into for loop it will do particular number of valid requests. As soon as I'm getting Timeout exception, the next request will be without POST data.
Source code is edited for those who thinks that recursion is a bad idea. The edited code still doesn't work.
Any suggestions are appreciated
I cant find anything wrong in your code, so provide mode details, as the comments mentioned.
private void request_3()
{
bool sendData = true;
int numberOfTimeOuts = 0;
// The follwing only needs to be done only once, unless you alter post_data_final after each timeout.
byte[] dataToSend = Encoding.ASCII.GetBytes(post_data_final);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(site_URI);
using (Stream outputStream = request.GetRequestStream())
outputStream.Write(dataToSend, 0, dataToSend.Length);
// request.TimeOut = 1000 * 15; would mean 15 Seconds.
while(sendData && numberOfTimeOuts < MAX_NUMBER_OF_TIMEOUTS)
{
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if(response != null)
processResponse(response);
else
{
//You should handle this case aswell.
}
sendData = false;
}
catch(WebException wex)
{
if (wex.Status == WebExceptionStatus.Timeout)
numberOfTimeOuts++;
else
throw;
}
}
}
The issue was because of using Fiddler2 - analogue of Wireshark (i.e. intercepting traffic tool).
Requested site uses https protocol. For debugging purposes I installed Fiddler2 and Fiddler2 certificate to be able to see all incoming and outcoming responses. For some magic reason when I turned off Fiddler2 and added some additional logging into console, i figured out that requests are appeared to be valid (i.e. POST body data still exists after first request).
So with Fiddler2 code above doesn't work while we're having Timeout exception. Without Fiddler2 everything works fine under the same circumstances and using the same code.
I didn't dig deep into Fiddler2, but seems to me issue could be only with compatibility of VS2010 and internal proxy for Error Codes (taking into account that using point 2 under "Updates" area (The Fiddler2 was also used there) for success codes (i.e. 2xx - 3xx) worked fine)
Thanks everyone for getting attention into this.
Harvest is the time tracking application that I use at my job. While the web UI is quite simple, there are a few custom features I would like to add. I noticed they have an API... So I want to make a custom desktop client in C# for it.
Just looking at the page, its not very informative. The C# sample that you can find (after doing some digging) doesn't help much either. So... How in the world do I use the API with C#?
Link to API page
Any help would be greatly appreciated :)
Harvest is using a REST API, so what is does is you do a get/put/post request to a web address on the server and it will return a result (usually formatted in XML or JSON (appears to be XML in this case)). A quick Google search returned this tutorial on how to use a REST API, hopefully that will be enough for what you need. If not, feel free to ask us about specific problems you are having using REST and C#
Here I will try to add some more comments to their sample:
using System;
using System.Net;
using System.IO;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
class HarvestSample
{
//This is used to validate the certificate the server gives you,
//it allays assumes the cert is valid.
public static bool Validator (object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
static void Main(string[] args)
{
//setting up the initial request.
HttpWebRequest request;
HttpWebResponse response = null;
StreamReader reader;
StringBuilder sbSource;
//1. Set some variables specific to your account.
//This is the URL that you will be doing your REST call against.
//Think of it as a function in normal library.
string uri = "https://yoursubdomain.harvestapp.com/projects";
string username="youremail#somewhere.com";
string password="yourharvestpassword";
string usernamePassword = username + ":" + password;
//This checks the SSL cert that the server will give us,
//the function is above this one.
ServicePointManager.ServerCertificateValidationCallback = Validator;
try
{
//more setup of the connection
request = WebRequest.Create(uri) as HttpWebRequest;
request.MaximumAutomaticRedirections = 1;
request.AllowAutoRedirect = true;
//2. It's important that both the Accept and ContentType headers
//are set in order for this to be interpreted as an API request.
request.Accept = "application/xml";
request.ContentType = "application/xml";
request.UserAgent = "harvest_api_sample.cs";
//3. Add the Basic Authentication header with username/password string.
request.Headers.Add("Authorization", "Basic " + Convert.
ToBase64String(new ASCIIEncoding().GetBytes(usernamePassword)));
//actually perform the GET request
using (response = request.GetResponse() as HttpWebResponse)
{
//Parse out the XML it returned.
if (request.HaveResponse == true && response != null)
{
reader = new StreamReader(response.GetResponseStream(),
Encoding.UTF8);
sbSource = new StringBuilder(reader.ReadToEnd());
//4. Print out the XML of all projects for this account.
Console.WriteLine(sbSource.ToString());
}
}
}
catch (WebException wex)
{
if (wex.Response != null)
{
using (HttpWebResponse errorResponse = (HttpWebResponse)wex.Response)
{
Console.WriteLine(
"The server returned '{0}' with the status code {1} ({2:d}).",
errorResponse.StatusDescription, errorResponse.StatusCode,
errorResponse.StatusCode);
}
}
else
{
Console.WriteLine( wex);
}
}
finally
{
if (response != null) { response.Close(); }
}
}
}
I've also struggled with their API. Scott's answer is very useful.
Anyway there is a very useful and easy library which is called EasyHttp witch you can find in NuGet.
here is the same method as Scott's but much shorter :):
public static string getProjects()
{
string uri = "https://<companyname>.harvestapp.com/projects";
HttpClient http = new HttpClient();
//Http Header
http.Request.Accept = HttpContentTypes.ApplicationJson;
http.Request.ContentType = HttpContentTypes.ApplicationJson;
http.Request.SetBasicAuthentication(username, password);
http.Request.ForceBasicAuth = true;
HttpResponse response = http.Get(uri);
return response.RawText;
}
If you want to learn more about WebApi calls you can use Fidler or a more easier and RestClient which is a Firefox plugin.
With RestClient you can talk to rest servers directly, very helpful if you want to understand RESTful services.
I'm using a .Net MVC application as a simplified web service.
I've got an async method that I call:
public void RunQueue()
{
QueueDelegate queue = new QueueDelegate(Queue);
AsyncCallback completedCallback = new AsyncCallback(QueueCompleteCallback);
lock (_sync)
{
if (!queueIsRunning)
{
AsyncOperation async = AsyncOperationManager.CreateOperation(null);
queue.BeginInvoke(QueueCompleteCallback, async);
queueIsRunning = true;
}
}
}
and the hope is that when I call it, it starts the queue and then lets the user continue on with their day (they'll get an email when the queue's done).
As it stands right now, everything works fine except that instead of letting the user continue on, the webpage calling the "web service" just hangs and the request eventually times out.
How do I build an HttpWebResponse and send it back to the other server so that the user can continue on?
I've tried having it return things other than "void" but that doesn't do much.
Here's the Controller that's calling it.
public ActionResult StartQueue()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:2394/Home/RunQueue/");
HttpWebResponse response;
string r = "";
try
{
response = (HttpWebResponse)request.GetResponse();
Stream stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
r = reader.ReadToEnd();
}
catch (WebException ex) // A WebException is not fatal. Record the status code.
{
response = (HttpWebResponse)ex.Response;
if (response != null) // timeout
{
r = response.StatusCode.ToString();
}
}
ViewData["message"] = r;
return View();
}
What is the QueueDelegate class? My guess would be that the request is waiting for the other thread to complete (maybe something like Thread.Join()?). I don't think sending a response is the solution you want - I'd suggest either finding a way to spawn a thread that is disconnected from the current request so that it ends naturally, or move the logic out completely into something like a Windows Service.
Hosting your long-running processes in the web context will be complicated at best :(