PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/2.0/trunk/dashCommerce.Net/Services/PaymentProvider/CyberSourcePaymentProvider.cs

#
C# | 385 lines | 271 code | 68 blank | 46 comment | 17 complexity | 7f549bbcd0541861ccc445b96cbc0c99 MD5 | raw file
Possible License(s): JSON
  1. using System;
  2. using System.Net;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.Configuration.Provider;
  6. using System.Configuration;
  7. using System.Web.Services.Protocols;
  8. using CyberSource.Clients;
  9. using CyberSource.Clients.SoapWebReference;
  10. namespace Commerce.Providers
  11. {
  12. /// <summary>
  13. /// BETA VERSION PROVIDED AS-IS
  14. /// 12/10/2007 - Paul Sterling
  15. /// Check for updates to this provider here:
  16. /// http://www.motusconnect.com/dashcommerce/providers.aspx
  17. /// Send feedback to: dc@motusconnect.com
  18. ///
  19. ///
  20. /// This CyberSourcePaymentProvider uses SOAP to submit requests
  21. /// and retrieve responses from CyberSource
  22. /// </summary>
  23. public class CyberSourcePaymentProvider : PaymentProvider
  24. {
  25. public CyberSourcePaymentProvider()
  26. { }
  27. #region Passed-in Properties
  28. string merchantID = "";
  29. string keysDirectory = "";
  30. bool sendToProduction = false; // default set to false
  31. int connectionLimit = -1; // set to '-1' by default (2 connections)
  32. bool authService = false; // default set to false
  33. bool captureService = false; // default set to false
  34. string currencyCode = "";
  35. #endregion
  36. #region Provider specific behaviors
  37. public override void Initialize(string name, NameValueCollection config)
  38. {
  39. // Verify that config isn't null
  40. if (config == null)
  41. throw new ArgumentNullException("config");
  42. // Assign the provider a default name if it doesn't have one
  43. if (String.IsNullOrEmpty(name))
  44. {
  45. config.Remove("name");
  46. config.Add("name",
  47. "CyberSourcePaymentProvider");
  48. }
  49. // Add a default "description" attribute to config if the
  50. // attribute doesn't exist or is empty
  51. if (string.IsNullOrEmpty(config["description"]))
  52. {
  53. config.Remove("description");
  54. config.Add("description",
  55. "CyberSource Payment Provider");
  56. }
  57. base.Initialize(name, config);
  58. merchantID = config["merchantID"].ToString();
  59. if (String.IsNullOrEmpty(merchantID))
  60. throw new ProviderException("Empty CyberSource merchantID value");
  61. config.Remove("merchantID");
  62. keysDirectory = config["keysDirectory"].ToString();
  63. if (String.IsNullOrEmpty(keysDirectory))
  64. throw new ProviderException("Empty CyberSource keysDirectory value");
  65. config.Remove("keysDirectory");
  66. sendToProduction = bool.Parse(config["sendToProduction"]);
  67. authService = bool.Parse(config["authService"]);
  68. captureService = bool.Parse(config["captureService"]);
  69. currencyCode = config["currencyCode"].ToString();
  70. if (String.IsNullOrEmpty(currencyCode))
  71. throw new ProviderException("Empty CyberSource currencyCode value");
  72. config.Remove("currencyCode");
  73. }
  74. public override string Name
  75. {
  76. get
  77. {
  78. return null;
  79. }
  80. }
  81. #endregion
  82. #region Provider Methods
  83. public override Commerce.Common.Transaction Charge(Commerce.Common.Order order)
  84. {
  85. decimal dTotal = order.OrderTotal;
  86. int roundTo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalDigits;
  87. dTotal = Math.Round(dTotal, roundTo);
  88. // set up CyberSource config object
  89. // use the values set in the provider section of web.config
  90. CyberSource.Clients.Configuration config = new CyberSource.Clients.Configuration();
  91. config.MerchantID = merchantID;
  92. config.KeysDirectory = keysDirectory;
  93. config.SendToProduction = sendToProduction;
  94. config.ConnectionLimit = connectionLimit;
  95. // TODO: use this only for testing
  96. // config.Demo = true;
  97. // set up SOAP request
  98. RequestMessage request = new RequestMessage();
  99. // Credit Card Authorization - should always be true
  100. if (authService)
  101. {
  102. request.ccAuthService = new CCAuthService();
  103. request.ccAuthService.run = "true";
  104. }
  105. // Credit Card Capture
  106. if (captureService)
  107. {
  108. request.ccCaptureService = new CCCaptureService();
  109. request.ccCaptureService.run = "true";
  110. }
  111. // add required fields
  112. request.merchantReferenceCode = order.OrderNumber;
  113. request.comments = order.OrderGUID; // set the comment to the order GUID
  114. BillTo billTo = new BillTo();
  115. billTo.firstName = order.BillingAddress.FirstName;
  116. billTo.lastName = order.BillingAddress.LastName;
  117. billTo.street1 = order.BillingAddress.Address1;
  118. billTo.city = order.BillingAddress.City;
  119. billTo.state = order.BillingAddress.StateOrRegion;
  120. billTo.postalCode = order.BillingAddress.Zip;
  121. billTo.country = order.BillingAddress.Country;
  122. billTo.email = order.Email;
  123. billTo.ipAddress = order.UserIP.ToString();
  124. request.billTo = billTo;
  125. Card card = new Card();
  126. card.accountNumber = order.CreditCardNumber;
  127. card.expirationMonth = order.CreditCardExpireMonth.ToString();
  128. card.expirationYear = order.CreditCardExpireYear.ToString();
  129. card.cvNumber = order.CreditCardSecurityNumber;
  130. request.card = card;
  131. PurchaseTotals purchaseTotals = new PurchaseTotals();
  132. purchaseTotals.currency = currencyCode;
  133. purchaseTotals.taxAmount = order.TaxAmount.ToString();
  134. purchaseTotals.freightAmount = order.ShippingAmount.ToString();
  135. purchaseTotals.grandTotalAmount = dTotal.ToString();
  136. request.purchaseTotals = purchaseTotals;
  137. // add detail items
  138. int itemCount = order.Items.Count;
  139. request.item = new Item[itemCount];
  140. for (int i = 0; i < itemCount; i++)
  141. {
  142. Item item = new Item();
  143. item.id = i.ToString();
  144. item.unitPrice = order.Items[i].PricePaid.ToString();
  145. item.totalAmount = order.Items[i].LineTotal.ToString();
  146. item.productName = order.Items[i].ProductName;
  147. item.quantity = order.Items[i].Quantity.ToString();
  148. request.item[i] = item;
  149. }
  150. // you can add optional fields here per your business needs
  151. // charge the card and get the return data to see what happened
  152. // I've kept the inspection to a minimum
  153. // this could be more robust if you have lots of failures
  154. Commerce.Common.Transaction trans = new Commerce.Common.Transaction();
  155. try
  156. {
  157. ReplyMessage reply = CyberSource.Clients.SoapClient.RunTransaction(config, request);
  158. if ("ACCEPT".Equals(reply.decision.ToUpper()))
  159. {
  160. trans.GatewayResponse = reply.decision;
  161. trans.TransactionNotes = ProcessReply(reply);
  162. trans.AuthorizationCode = reply.ccAuthReply.authorizationCode;
  163. }
  164. if ("REJECT".Equals(reply.decision.ToUpper()))
  165. {
  166. throw new Exception("Declined: " + ProcessReply(reply));
  167. }
  168. }
  169. // some specific error handlers
  170. catch (SignException se)
  171. {
  172. throw new Exception("Gateway Error: " + HandleSignException(se));
  173. }
  174. catch (SoapHeaderException she)
  175. {
  176. throw new Exception("Gateway Error: " + HandleSoapHeaderException(she));
  177. }
  178. catch (SoapBodyException sbe)
  179. {
  180. throw new Exception("Gateway Error: " + HandleSoapBodyException(sbe));
  181. }
  182. catch (WebException we)
  183. {
  184. throw new Exception("Gateway Error: " + HandleWebException(we));
  185. }
  186. catch (Exception e)
  187. {
  188. throw new Exception("Gateway Error - undefined: " + e.Message);
  189. }
  190. // Return the tranasaction object for use by the Business Controller
  191. return trans;
  192. }
  193. public override string Refund(Commerce.Common.Order order)
  194. {
  195. throw new Exception("This method not enabled for CyberSource Payment Provider");
  196. }
  197. private static string ProcessReply(ReplyMessage reply)
  198. {
  199. string content = GetContent(reply);
  200. return content;
  201. }
  202. private static string GetContent(ReplyMessage reply)
  203. {
  204. int reasonCode = int.Parse(reply.reasonCode);
  205. switch (reasonCode)
  206. {
  207. // Success
  208. case 100:
  209. return (
  210. "\nRequest ID: " + reply.requestID +
  211. "\nAuthorization Code: " +
  212. reply.ccAuthReply.authorizationCode +
  213. "\nCapture Request Time: " +
  214. reply.ccCaptureReply.requestDateTime +
  215. "\nCaptured Amount: " +
  216. reply.ccCaptureReply.amount);
  217. // Missing field(s)
  218. case 101:
  219. return (
  220. "\nThe following required field(s) are missing: " +
  221. EnumerateValues(reply.missingField));
  222. // Invalid field(s)
  223. case 102:
  224. return (
  225. "\nThe following field(s) are invalid: " +
  226. EnumerateValues(reply.invalidField));
  227. // Insufficient funds
  228. case 204:
  229. return (
  230. "\nInsufficient funds in the account. Please use a " +
  231. "different card or select another form of payment.");
  232. // Bad account number
  233. case 231:
  234. return (
  235. "\nInvalid account number. Please check the account " +
  236. "number.");
  237. // add additional reason codes here that you need to handle
  238. default:
  239. // For all other reason codes, return an empty string,
  240. return (String.Empty);
  241. }
  242. }
  243. private static string HandleSignException(SignException se)
  244. {
  245. string content = String.Format(
  246. "\nFailed to sign the request with error code '{0}' and " +
  247. "message '{1}'.", se.ErrorCode, se.Message);
  248. return content;
  249. }
  250. private static string HandleSoapHeaderException(
  251. SoapHeaderException she)
  252. {
  253. string content = String.Format(
  254. "\nA SOAP header exception was returned with fault code " +
  255. "'{0}' and message '{1}'.", she.Code, she.Message);
  256. return content;
  257. }
  258. private static string HandleSoapBodyException(SoapBodyException sbe)
  259. {
  260. string content = String.Format(
  261. "\nA SOAP body exception was returned with fault code " +
  262. "'{0}' and message '{1}'.", sbe.Code, sbe.Message);
  263. return content;
  264. }
  265. private static string HandleWebException(WebException we)
  266. {
  267. string content = String.Format(
  268. "\nFailed to get a response with status '{0}' and " +
  269. "message '{1}'", we.Status, we.Message);
  270. if (IsCriticalError(we))
  271. {
  272. // log this event as the payment may have been
  273. // successfully processed
  274. }
  275. return content;
  276. }
  277. private static string EnumerateValues(string[] array)
  278. {
  279. System.Text.StringBuilder sb = new System.Text.StringBuilder();
  280. foreach (string val in array)
  281. {
  282. sb.Append(val + "\n");
  283. }
  284. return (sb.ToString());
  285. }
  286. private static bool IsCriticalError(WebException we)
  287. {
  288. switch (we.Status)
  289. {
  290. case WebExceptionStatus.ProtocolError:
  291. if (we.Response != null)
  292. {
  293. HttpWebResponse response
  294. = (HttpWebResponse)we.Response;
  295. // GatewayTimeout may be returned if you are
  296. // connecting through a proxy server.
  297. return (response.StatusCode ==
  298. HttpStatusCode.GatewayTimeout);
  299. }
  300. // In case of ProtocolError, the Response property
  301. // should always be present. In the unlikely case
  302. // that it is not, we assume something went wrong
  303. // along the way and to be safe, treat it as a
  304. // critical error.
  305. return (true);
  306. case WebExceptionStatus.ConnectFailure:
  307. case WebExceptionStatus.NameResolutionFailure:
  308. case WebExceptionStatus.ProxyNameResolutionFailure:
  309. case WebExceptionStatus.SendFailure:
  310. return (false);
  311. default:
  312. return (true);
  313. }
  314. }
  315. #endregion
  316. }
  317. }