I'm currently working on my very first Umbraco project which has been both rewarding and a bit frustrating. The client I'm working with would like to use the Authorize.NET DPM payment method rather than the default Authorize.NET payment provider so that they can control the entire shopping cart checkout process.
I created a new payment provider and have overridden GenerateForm method which works great. I can get all the way up to the final step of this process below; however, when the user is redirected the confirmation page all the fields are blank because the order is never finalized.
Having said that, I have four questions (probably more):
The first gold arrow going from Payment Gateway to Merchant Server represents a POST from Authorize.NET to my client's site. I've setup a /Base service to handle this call. Can/Should I finalize the order there once I verify the payment was successful. If so how?
In the /Base service noted above, is it possible for me to access my payment provider settings? I'd like to get values such as x_login and the success and failure URLs that Authorize.NET should direct the user to upon processing the payment. Right now these values are hardcoded.
If I have both Authorize.NET DPM, and standard Authorize.NET payment providers setup in that order - can I use Authorize.NET DPM to control the checkout process on the web site and use the standard Authorize.NET provider that comes with TeaCommerce handle all the web service communication?
This is superficial, but how can I make the settings for my Authorize.NET DPM payment provider have friendly names in the admin interface like the standard payment provider. For example, my payment provider in the first image vs the standard payment provider in the second.
Yes you should verify the order here - you should be able to have the umbraco callback in the generated form. This is the url you should have Authrozize.NET make the relay response to. I think maybe with the Authorize.NET provider that you have to specify the relay url in the admin interface. But you can read about the silent post url in my blog post - http://anders.burla.dk/umbraco/tea-commerce/using-authorize-net-with-tea-commerce/ Your provider should override the AllowCallbackWithOrderId and return true AND you should override the GetOrderId method to verify the request from the relay and then return the order id.
Base should not have access to the payment provider info as javascript is running client side - so all should be server side to be secure
Dont think its the right solution - I think to make the inline form payment possible you have to make your payment provider and then a user control to have the payment form where the customer enters the card number and those info. Then you have to redirect to your payment provider when you need to. Maybe this blog post can give you an idea - our.umbraco.org/projects/website-utilities/tea-commerce/tea-commerce-support/26049-Payment-provider-with-own-form
Look in the en.xml file (normal umbraco translation file) for the Tea Commerce section and the different keys - the key is made of the payment provider alias and the setting key
Thanks for the response...exactly what I needed to move forward. Being a new to Umbraco this project was a little daunting at first and I still have a lot to learn - so I'm very thankful you could shed some light on everything.
I'll let you know how it goes and share a link once the site is live!
Your welcome - hope you like working with umbraco - its a great system! But yes - an e-commerce project as your first umbraco project is a little tougher start than a simple and plain forward website :)
I tried implementing the the suggestions you mentioned above regarding the overriding of AllowCallbackWithOutOrderId property and GetOrderId method - but can't seem to get it working.
You mentioned, "Yes you should verify the order here - you should be able to have the umbraco callback in the generated form." but I'm not quite sure what you meant by that. I assumed you meant the ProcessCallback method of the payment provider so I overrode that and put my order verification code there.
The problem I'm having now is that it seems as if my code is never being called - I've attached a debugger and my breakpoints never get hit. I currently have the following payment methods setup in my site:
Also, is there anyway I can modify the response sent back to Authorize.NET from their relay response call into my client's site? For a successful verification, they require an HTTP 200 response with a specially crafted body. For unsuccessful verifications, they require 302 to the page you want to redirect the user to.
Could you send us an email - info(at)teacommerce.dk and I will send you our code for the out of the box Authorize.Net payment provider - might give you an idea of what to do.
Your relay code is hard to debug as it has to be at the live domain so Authroize can call the live url. You can write the data to a file to see what info you get. But I think that you have to specify the relay url in the Authorize.Net admin interface - but yes your url looks correct.
Your payment provider is just a normal http post/get call from Authorize.Net so yes you can make a 200 or 302 response i think.
Did you ever figure out how to set the response code inside your ProcessCallback method? I am able to successfully post a transaction and complete the transaction and order in the ProcessCallback method, but I receive an error on the test.authorize.net/gateway/transact.dll page.
Error Details: Your script timed out while we were trying to post transaction results to it.
I am assuming that Authorize.NET is returning a timeout since I am not setting the response status value appropriately.
I am setting the response code as follows- HttpContext.Current.Response.StatusCode = 200;
No luck though.
Also, when I run a transaction that I know will fail (invalid card number/expiration date) it appears that my ProcessCallback method is never being called. I am logging in multiple places in the method and none are being executed.
Don't know about the timeout - I never experienced that. Is your code invoked? If yes - should the response type be anything regarding the documentation of Authorize.NET?
In the invalid card number - try and log in your cancel url and see if that is called instead - something must be called :)
I've got this working now. Authorize.NET's documentation is a little bit confusing and I had assumed that setting the response value to 200 OK would cause a redirect to my continue url if the payment was successful. I also assumed the same would be true for an error. If I set the status appropriately, then Authorize.Net would redirect for me. That was not the case.
I'll explain as best as I can.
When a transaction is posted to Authorize.NET using DPM, they attempt to process the transaction and then send a call into your relay url (defined in the form as "x_relay_url" and optionally in your merchant account settings). For your payment provider this would be something like http://www.domain.com/tcbase/teacommerce/PaymentCallback/AuthorizeNetDpm/0.aspx.
They are expecting a specially crafted response that would then display on the gateway page at Authorize.Net. You could create the HTML that you want them to display using absolute URL's for any images or links and make it look like a page in your site. Or a better option would be to create HTML that redirects back to your site. When Authorize.Net renders the HTML response you made to the client, the browser redirects to your site.
If my ProcessCallback method validates the transaction and order successfully, I pass the continue url to the EndWithRedirect() method. If there is a problem, I pass my payment page url and append the response code to the query string. The usercontrol macro I have on the payment page will display the appropriate error message and let the user attempt the payment again.
NOTE - When there was an error with the payment, the ProcessCallback method was never called because the GetOrderID method was returning null. I needed to add the same EndWithRedirect call in the GetOrderId method as well.
One quick update to this for anyone following along - please remove the HttpContext.Current.Response.End() from the Redirect method. It was preventing the callback from being returned so the order was not being updated.
Thanks for your follow-up posts regarding your work done on the Authorize.NET DPM provider. Are you at liberty to share your final working DPM provider with me? My client put the project on hold for a couple months and recently started moving forward with it...so I need to get this working and would be forever in debt to you for your help!
If you can't share, no worries...I totally understand.
Alright everyone, I'm almost home...just having a minor issue with the order not displaying when the user is redirected to the /cart/confirmation page.
Turns out I had everything correct before EXCEPT I was using http://domain.com/tcbase/teacommerce/PaymentCallback/AuthorizeNetDpm/0.aspx as the URL for Authorize.NET to post back to rather than the value of the teaCommerceCallBackUrl paramter in the GetForm method. Once that was changed the calls from Authorize.NET began hitting my ProcessCallback method which enabled me to handle them appropriately.
As I mentioned above, the issue I'm running into now is that the /cart/confirmation page is blank when the user is redirected to it via Authorize.NET. The interesting thing is that the order's payment status is captured within Tea Commerce admin section.
I've tried a combination of setting FinalizeAtContinueUrl to true and false and redirecting the user to the value of the teaCommerceContinueUrl parameter of the GetForm method.
I'm clearly doing something wrong and would greatly appreciate any assistance.
Its good that the order is now marked as captured - this means your finalize code works. Which method do you use at the confirmation page to get the order?
Sorry for the late response. I've been out for a while for the birth of my son and then I had to make sure the NDA with this client allowed me to share the final source. Here is the my Authorize.Net DPM payment provider. I hope it helps. Your thread really helped me get this and I'd love to return the favor.
Sean
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using System.Xml.XPath;
using Tea.Utils.Extension;
using TeaCommerce.Data.Payment;
using TeaCommerce.PaymentProviders.AuthorizeNetService;
using TeaCommerce.Data;
using umbraco.BusinessLogic;
using System.Globalization;
using System.IO;
using TeaCommerce.Data.Tools;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Text;
using System.Net.Mail;
using System.Configuration;
namespace YourProject.TeaCommerceExtensions
{
public class AuthorizeNetDPM : APaymentProvider
{
protected bool isTesting;
public override bool AllowsCancelPayment { get { return false; } }
public override bool AllowsCapturePayment { get { return false; } }
public override bool AllowsRefundPayment { get { return false; } }
public override Dictionary DefaultSettings
{
get
{
if (defaultSettings == null)
{
defaultSettings = new Dictionary();
defaultSettings["x_login"] = string.Empty;
defaultSettings["x_receipt_link_url"] = string.Empty;
defaultSettings["x_cancel_url"] = string.Empty;
defaultSettings["x_type"] = "AUTH_ONLY";
defaultSettings["transactionKey"] = string.Empty;
defaultSettings["md5HashKey"] = string.Empty;
defaultSettings["testing"] = "0";
defaultSettings["x_error_url"] = string.Empty;
}
return defaultSettings;
}
}
//public override string FormPostUrl { get { return !isTesting ? "https://secure.authorize.net/gateway/transact.dll" : "https://test.authorize.net/gateway/transact.dll"; } }
public override string FormPostUrl
{
get
{
return "http://" + HttpContext.Current.Request.ServerVariables["SERVER_NAME"].ToString() + "/cart/payment.aspx";
}
}
public override string DocumentationLink { get { return "http://anders.burla.dk/umbraco/tea-commerce/using-authorize-net-with-tea-commerce/"; } }
public override bool AllowCallbackWithoutOrderId { get { return true; } }
public override Dictionary GenerateForm(Order order, string teaCommerceContinueUrl, string teaCommerceCancelUrl, string teaCommerceCallBackUrl, Dictionary settings)
{
List settingsToExclude = new string[] { "transactionKey", "md5HashKey", "testing" }.ToList();
Dictionary inputFields = settings.Where(i => !settingsToExclude.Contains(i.Key)).ToDictionary(i => i.Key, i => i.Value);
isTesting = settings["testing"].ParseToBool(false);
//Future: Would be cool to support item lines for this one - you have to return a List> for it to work with this provider
inputFields["x_version"] = "3.1";
inputFields["x_show_form"] = "PAYMENT_FORM";
inputFields["x_relay_always"] = "TRUE";
inputFields["x_relay_response"] = "TRUE";
inputFields["x_receipt_link_method"] = "LINK";
inputFields["x_invoice_num"] = order.Name;
string amount = order.TotalPrice.ToString("0.00", CultureInfo.InvariantCulture);
inputFields["x_amount"] = amount;
inputFields["x_receipt_link_url"] = teaCommerceContinueUrl;
inputFields["x_cancel_url"] = teaCommerceCancelUrl;
inputFields["x_error_url"] = settings["x_error_url"];
string sequenceNumber = order.Id.ToString();
inputFields["x_fp_sequence"] = sequenceNumber;
string timestamp = (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds.ToString("0", CultureInfo.InvariantCulture);
inputFields["x_fp_timestamp"] = timestamp;
inputFields["x_fp_hash"] = EncryptHMAC(settings["transactionKey"], settings["x_login"] + "^" + sequenceNumber + "^" + timestamp + "^" + amount + "^");
//order specific fields
inputFields.Add("x_order_id", order.Id.ToString());
inputFields.Add("x_first_name", order.GetPropertyValue("firstName"));
inputFields.Add("x_last_name", order.GetPropertyValue("lastName"));
inputFields.Add("x_company", order.GetPropertyValue("company"));
inputFields.Add("x_address", order.GetPropertyValue("streetAddress"));
inputFields.Add("x_city", order.GetPropertyValue("city"));
inputFields.Add("x_state", order.GetPropertyValue("state"));
inputFields.Add("x_zip", order.GetPropertyValue("zipCode"));
inputFields.Add("x_country", order.GetPropertyValue("country"));
inputFields.Add("x_email", order.GetPropertyValue("email"));
inputFields.Add("x_ship_to_first_name", order.GetPropertyValue("shipping_firstName"));
inputFields.Add("x_ship_to_last_name", order.GetPropertyValue("shipping_lastName"));
inputFields.Add("x_ship_to_company", order.GetPropertyValue("shipping_company"));
inputFields.Add("x_ship_to_address", order.GetPropertyValue("shipping_streetAddress"));
inputFields.Add("x_ship_to_city", order.GetPropertyValue("shipping_city"));
inputFields.Add("x_ship_to_state", order.GetPropertyValue("shipping_state"));
inputFields.Add("x_ship_to_zip", order.GetPropertyValue("shipping_zipCode"));
inputFields.Add("x_tax", order.TotalVAT.ToString());
inputFields.Add("x_freight", order.ShippingFee.ToString());
if (inputFields.ContainsKey("x_login"))
{
inputFields["x_login"] = settings["x_login"];
}
else
{
inputFields.Add("x_login", settings["x_login"]);
}
return inputFields;
}
public override string GetContinueUrl(Dictionary settings)
{
SendMail("GetContinueUrl", "");
return settings["x_receipt_link_url"];
}
public override string GetCancelUrl(Dictionary settings)
{
SendMail("GetCancelUrl", "");
return settings["x_cancel_url"];
}
string BuildMessage(HttpRequest request)
{
string built = request.QueryString.ToString() + System.Environment.NewLine;
built += request.RawUrl.ToString() + System.Environment.NewLine;
foreach (string k in request.Form.Keys)
{
built += k + ": " + request.Form[k] + System.Environment.NewLine;
}
return built;
}
public override long? GetOrderId(HttpRequest request, Dictionary settings)
{
SendMail("GetOrderId", BuildMessage(request));
string errorMessage = string.Empty;
string responseCode = request.Form["x_response_code"];
string responseReasonCode = request.Form["x_response_reason_code"];
if (responseCode.Equals("1"))
{
string amount = request.Form["x_amount"];
string transaction = request.Form["x_trans_id"];
string gatewayMd5Hash = request.Form["x_MD5_Hash"];
MD5CryptoServiceProvider x = new MD5CryptoServiceProvider();
string calculatedMd5Hash = Regex.Replace(BitConverter.ToString(x.ComputeHash(Encoding.ASCII.GetBytes(settings["md5HashKey"] + settings["x_login"] + transaction + amount))), "-", string.Empty);
if (gatewayMd5Hash.Equals(calculatedMd5Hash))
{
string orderName = request.Form["x_invoice_num"];
return orderName.Replace(TeaCommerceSettings.OrderNamePrefix, string.Empty).ParseToLong();
}
else
errorMessage = "Tea Commerce - Authorize.net - MD5Sum security check failed - " + gatewayMd5Hash + " - " + calculatedMd5Hash + " - " + settings["md5HashKey"];
}
else
errorMessage = "Tea Commerce - Authorize.net - Payment not approved: " + responseCode;
EndWithRedirect(String.Format("{0}/?RC={1}&RS={2}", settings["x_error_url"], responseCode, responseReasonCode));
Log.Add(LogTypes.Error, -1, errorMessage);
return null;
}
public override CallbackInfo ProcessCallback(Order order, HttpRequest request, Dictionary settings)
{
SendMail("Process Callback - started", BuildMessage(request));
string errorMessage = string.Empty;
string responseCode = request.Form["x_response_code"];
string responseReasonCode = request.Form["x_response_reason_code"];
string receiptURL = settings["x_receipt_link_url"];
if (responseCode.Equals("1"))
{
string amount = request.Form["x_amount"];
string transaction = request.Form["x_trans_id"];
string gatewayMd5Hash = request.Form["x_MD5_Hash"];
MD5CryptoServiceProvider x = new MD5CryptoServiceProvider();
string calculatedMd5Hash = Regex.Replace(BitConverter.ToString(x.ComputeHash(Encoding.ASCII.GetBytes(settings["md5HashKey"] + settings["x_login"] + transaction + amount))), "-", string.Empty);
if (gatewayMd5Hash.Equals(calculatedMd5Hash))
{
string orderName = request.Form["x_invoice_num"];
PaymentStatus paymentStatus = PaymentStatus.Authorized;
if (request.Form["x_type"].Equals("auth_capture"))
paymentStatus = PaymentStatus.Captured;
string cardType = request.Form["x_card_type"];
string cardNumber = request.Form["x_account_number"];
SendMail("ProcessCallback", "finished" + orderName + "receipt url:" + receiptURL);
EndWithRedirect(String.Format("{0}?OID={1}", receiptURL, order.Id));
return new CallbackInfo(orderName, amount.ParseToDecimal(CultureInfo.InvariantCulture, 0), transaction, paymentStatus, cardType, cardNumber);
}
else
errorMessage = "Tea Commerce - Authorize.net - MD5Sum security check failed - " + gatewayMd5Hash + " - " + calculatedMd5Hash + " - " + settings["md5HashKey"];
}
else
errorMessage = "Tea Commerce - Authorize.net - Payment not approved: " + responseCode;
EndWithRedirect(String.Format("{0}/?RC={1}&RS={2}", settings["x_error_url"], responseCode, responseReasonCode));
SendMail("ProcessCallback", "finished" + errorMessage);
Log.Add(LogTypes.Error, -1, errorMessage);
return new CallbackInfo(errorMessage);
}
public override APIInfo GetStatus(Order order, Dictionary settings)
{
string errorMessage = string.Empty;
GetTransactionDetailsResponseType result = GetAuthorizeNetServiceClient(settings).GetTransactionDetails(new MerchantAuthenticationType() { name = settings["x_login"], transactionKey = settings["transactionKey"] }, order.TransactionPaymentTransactionId);
if (result.resultCode == MessageTypeEnum.Ok)
{
PaymentStatus paymentStatus = PaymentStatus.Initial;
switch (result.transaction.transactionStatus)
{
case "authorizedPendingCapture":
paymentStatus = PaymentStatus.Authorized;
break;
case "capturedPendingSettlement":
case "settledSuccessfully":
paymentStatus = PaymentStatus.Captured;
break;
case "voided":
paymentStatus = PaymentStatus.Cancelled;
break;
case "refundSettledSuccessfully":
case "refundPendingSettlement":
paymentStatus = PaymentStatus.Refunded;
break;
}
return new APIInfo(result.transaction.transId, paymentStatus);
}
else
{
errorMessage = "Tea Commerce - Authorize.net - " + string.Format(umbraco.ui.Text("teaCommerce", "paymentProvider_AuthorizeNet_error"), result.messages[0].code, result.messages[0].text);
}
Log.Add(LogTypes.Error, -1, errorMessage);
return new APIInfo(errorMessage);
}
public override APIInfo CapturePayment(Order order, Dictionary settings)
{
throw new NotImplementedException();
}
public override APIInfo RefundPayment(Order order, Dictionary settings)
{
throw new NotImplementedException();
}
public override APIInfo CancelPayment(Order order, Dictionary settings)
{
throw new NotImplementedException();
}
protected Service GetAuthorizeNetServiceClient(Dictionarysettings)
{
Service service = new Service();
service.Url = !settings["testing"].ParseToBool(false) ? "https://api.authorize.net/soap/v1/Service.asmx" : "https://apitest.authorize.net/soap/v1/Service.asmx";
return service;
}
void SendMail(string subject, string body)
{
if(!String.IsNullOrEmpty(ConfigurationManager.AppSettings["paymentDiagnosticEmail"]))
{
MailMessage mail = new MailMessage();
mail.To.Add(new MailAddress(ConfigurationManager.AppSettings["paymentDiagnosticEmail"]));
mail.From = new MailAddress(ConfigurationManager.AppSettings["paymentDiagnosticEmail"]);
mail.Subject = subject;
mail.IsBodyHtml = false;
mail.Body = body;
SmtpClient client = new SmtpClient();
client.Send(mail);
}
}
void EndWithRedirect(string strURL){
HttpContext.Current.Response.Write("
I was hoping the fact that the order was showing captured in the admin tools was a good sign! As for the confirmation page, I'm using the GetFinalizedOrderXml call to retrieve the order - I'm using a basically unmodifed version of cart_step06.xslt from the starter kit.
Is it ok that I'm redirecting the user back to the teaCommerceCallBackUrl and not the teaCommerceContinueUrl? It almost seems as if Umbraco/TeaCommerce is thinking the user is a new session when they are redirected from Authorize.NET.
Thanks for posting your payment provider code - I wasn't sure if you'd be able to b/c of NDA or other contractual obligations. The cool thing is that I have almost exactly the same thing, so we're definitely on the same page. I noticed that you're passing the ID of the order in your redirects to the confirmation and cancel pages. Were you having the same problem I currently am with the finalized order not displaying on the confirmation page?
Ryan - It was a couple of months ago and I can't remember if that was the exact reason I did it that way, but looking back at the code it seems pretty likely.
Here are a few lines from my cart_step06 where I grab the order xml. Hope it helps.
<xsl:variable name="order_id" select="umbraco.library:RequestQueryString('OID')"/>
<!-- The Order -->
<xsl:variable name="order" select="teacommerce:GetSpecificOrderXml($order_id)" />
Yeah, it definitely looks like you were having the same issue. I think I'll probably go down the same path unless Anders has any ideas as to why it's not coming back automatically.
Thanks again for all your help - I really appreciate it!
As a note - you should always redirect the customer to the continue url - because this is the Tea Commerce code that moves the order in session. Now if you want your callback method invoked at the continue url you just override the property and return true to do this. This should invoke the callback url within the same sync redirect.
Authorize.NET Direct Post Method Payment Provider
Hi Everyone,
I'm currently working on my very first Umbraco project which has been both rewarding and a bit frustrating. The client I'm working with would like to use the Authorize.NET DPM payment method rather than the default Authorize.NET payment provider so that they can control the entire shopping cart checkout process.
I created a new payment provider and have overridden GenerateForm method which works great. I can get all the way up to the final step of this process below; however, when the user is redirected the confirmation page all the fields are blank because the order is never finalized.
Having said that, I have four questions (probably more):
Thanks in advance for any help you can provide.
Ryan
Hi Ryan
Thanks for your great questions.
Your provider should override the AllowCallbackWithOrderId and return true AND you should override the GetOrderId method to verify the request from the relay and then return the order id.
Kind regards
Anders
Hi Anders,
Thanks for the response...exactly what I needed to move forward. Being a new to Umbraco this project was a little daunting at first and I still have a lot to learn - so I'm very thankful you could shed some light on everything.
I'll let you know how it goes and share a link once the site is live!
Thanks again,
Ryan
Hi Ryan
Your welcome - hope you like working with umbraco - its a great system! But yes - an e-commerce project as your first umbraco project is a little tougher start than a simple and plain forward website :)
Kind regards
Anders
Hi Anders,
I tried implementing the the suggestions you mentioned above regarding the overriding of AllowCallbackWithOutOrderId property and GetOrderId method - but can't seem to get it working.
You mentioned, "Yes you should verify the order here - you should be able to have the umbraco callback in the generated form." but I'm not quite sure what you meant by that. I assumed you meant the ProcessCallback method of the payment provider so I overrode that and put my order verification code there.
The problem I'm having now is that it seems as if my code is never being called - I've attached a debugger and my breakpoints never get hit. I currently have the following payment methods setup in my site:
Would my relay response URL be: http://domain.com/tcbase/teacommerce/PaymentCallback/AuthorizeNetDpm/0.aspx ?
Also, is there anyway I can modify the response sent back to Authorize.NET from their relay response call into my client's site? For a successful verification, they require an HTTP 200 response with a specially crafted body. For unsuccessful verifications, they require 302 to the page you want to redirect the user to.
Thanks again for any help you can provide!
Ryan
Hi Ryan
Could you send us an email - info(at)teacommerce.dk and I will send you our code for the out of the box Authorize.Net payment provider - might give you an idea of what to do.
Your relay code is hard to debug as it has to be at the live domain so Authroize can call the live url. You can write the data to a file to see what info you get. But I think that you have to specify the relay url in the Authorize.Net admin interface - but yes your url looks correct.
Your payment provider is just a normal http post/get call from Authorize.Net so yes you can make a 200 or 302 response i think.
Kind regards
Anders
Hi Anders/Ryan,
Did you ever figure out how to set the response code inside your ProcessCallback method? I am able to successfully post a transaction and complete the transaction and order in the ProcessCallback method, but I receive an error on the test.authorize.net/gateway/transact.dll page.
Error Details: Your script timed out while we were trying to post transaction results to it.
I am assuming that Authorize.NET is returning a timeout since I am not setting the response status value appropriately.
I am setting the response code as follows- HttpContext.Current.Response.StatusCode = 200;
No luck though.
Also, when I run a transaction that I know will fail (invalid card number/expiration date) it appears that my ProcessCallback method is never being called. I am logging in multiple places in the method and none are being executed.
Any ideas?
Thanks for your time.
Hi Sean
Don't know about the timeout - I never experienced that. Is your code invoked? If yes - should the response type be anything regarding the documentation of Authorize.NET?
In the invalid card number - try and log in your cancel url and see if that is called instead - something must be called :)
Kind regards
Anders
Hi Anders,
I've got this working now. Authorize.NET's documentation is a little bit confusing and I had assumed that setting the response value to 200 OK would cause a redirect to my continue url if the payment was successful. I also assumed the same would be true for an error. If I set the status appropriately, then Authorize.Net would redirect for me. That was not the case.
I'll explain as best as I can.
When a transaction is posted to Authorize.NET using DPM, they attempt to process the transaction and then send a call into your relay url (defined in the form as "x_relay_url" and optionally in your merchant account settings). For your payment provider this would be something like http://www.domain.com/tcbase/teacommerce/PaymentCallback/AuthorizeNetDpm/0.aspx.
They are expecting a specially crafted response that would then display on the gateway page at Authorize.Net. You could create the HTML that you want them to display using absolute URL's for any images or links and make it look like a page in your site. Or a better option would be to create HTML that redirects back to your site. When Authorize.Net renders the HTML response you made to the client, the browser redirects to your site.
I used a sample I found here to get this working - http://www.tandasoft.com/2011/05/05/using-authorize-net-dpm-direct-post-method-from-asp-net-web-forms/
Code for this at the end of the post -
If my ProcessCallback method validates the transaction and order successfully, I pass the continue url to the EndWithRedirect() method. If there is a problem, I pass my payment page url and append the response code to the query string. The usercontrol macro I have on the payment page will display the appropriate error message and let the user attempt the payment again.
NOTE - When there was an error with the payment, the ProcessCallback method was never called because the GetOrderID method was returning null. I needed to add the same EndWithRedirect call in the GetOrderId method as well.
void EndWithRedirect(string strURL){
HttpContext.Current.Response.Write("<html><head><script type='text/javascript' charset='utf-8'>");
HttpContext.Current.Response.Write("window.location='" + strURL + "';");
HttpContext.Current.Response.Write("</script><noscript><meta http-equiv='refresh' content='1;url=" + strURL + "'></noscript></head><body></body></html>");
HttpContext.Current.Response.End();
}
So long story short, this was just a problem with my understanding of the Authorize.Net documentation and all is working now.
One quick update to this for anyone following along - please remove the HttpContext.Current.Response.End() from the Redirect method. It was preventing the callback from being returned so the order was not being updated.
Hey Sean,
Thanks for your follow-up posts regarding your work done on the Authorize.NET DPM provider. Are you at liberty to share your final working DPM provider with me? My client put the project on hold for a couple months and recently started moving forward with it...so I need to get this working and would be forever in debt to you for your help!
If you can't share, no worries...I totally understand.
Thanks in advance!
Ryan
Alright everyone, I'm almost home...just having a minor issue with the order not displaying when the user is redirected to the /cart/confirmation page.
Turns out I had everything correct before EXCEPT I was using http://domain.com/tcbase/teacommerce/PaymentCallback/AuthorizeNetDpm/0.aspx as the URL for Authorize.NET to post back to rather than the value of the teaCommerceCallBackUrl paramter in the GetForm method. Once that was changed the calls from Authorize.NET began hitting my ProcessCallback method which enabled me to handle them appropriately.
As I mentioned above, the issue I'm running into now is that the /cart/confirmation page is blank when the user is redirected to it via Authorize.NET. The interesting thing is that the order's payment status is captured within Tea Commerce admin section.
I've tried a combination of setting FinalizeAtContinueUrl to true and false and redirecting the user to the value of the teaCommerceContinueUrl parameter of the GetForm method.
I'm clearly doing something wrong and would greatly appreciate any assistance.
Thanks in advance!
Ryan
Hi Ryan
Its good that the order is now marked as captured - this means your finalize code works. Which method do you use at the confirmation page to get the order?
Kind regards
Anders
Hey Ryan -
Sorry for the late response. I've been out for a while for the birth of my son and then I had to make sure the NDA with this client allowed me to share the final source. Here is the my Authorize.Net DPM payment provider. I hope it helps. Your thread really helped me get this and I'd love to return the favor.
Sean
Hey Anders,
I was hoping the fact that the order was showing captured in the admin tools was a good sign! As for the confirmation page, I'm using the GetFinalizedOrderXml call to retrieve the order - I'm using a basically unmodifed version of cart_step06.xslt from the starter kit.
Is it ok that I'm redirecting the user back to the teaCommerceCallBackUrl and not the teaCommerceContinueUrl? It almost seems as if Umbraco/TeaCommerce is thinking the user is a new session when they are redirected from Authorize.NET.
Thanks,
Ryan
Hey Sean,
Thanks for posting your payment provider code - I wasn't sure if you'd be able to b/c of NDA or other contractual obligations. The cool thing is that I have almost exactly the same thing, so we're definitely on the same page. I noticed that you're passing the ID of the order in your redirects to the confirmation and cancel pages. Were you having the same problem I currently am with the finalized order not displaying on the confirmation page?
Thanks again,
Ryan
Ryan - It was a couple of months ago and I can't remember if that was the exact reason I did it that way, but looking back at the code it seems pretty likely.
Here are a few lines from my cart_step06 where I grab the order xml. Hope it helps.
Yeah, it definitely looks like you were having the same issue. I think I'll probably go down the same path unless Anders has any ideas as to why it's not coming back automatically.
Thanks again for all your help - I really appreciate it!
Take care,
Ryan
Hi Ryan
As a note - you should always redirect the customer to the continue url - because this is the Tea Commerce code that moves the order in session. Now if you want your callback method invoked at the continue url you just override the property and return true to do this. This should invoke the callback url within the same sync redirect.
Kind regards
Anders
is working on a reply...