How to use the Elavon Converge Payment Gateway lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- RedOctober · 2021-05-28 21:22 Platform:  Lazarus 2.0.10, FPC 3.2.0, Brook 5 Framework 5.4.9,  Microsoft Server 2016 I am trying to send a token request to the Elavon Converge server.  When I use simple Key=Value pairs in the request body, it works.  But when I use, what I call a "XML" style format in the body, it fails with a "bad request".   The Elavon Converge help cannot understand what is wrong, but they said I shd be using PHP.  How do I send a Request body with the "XML" style formatting, in PHP? Here is what I'm trying to emulate.  I have all the correct authorization numbers:  (All examples are in the "XML" sytle) https://developer.elavon.com/na/docs/converge/1.0.0/integration- guide/transaction_types/credit_card/sale Here is what works, I can get a token from Elavon using this formatting in the body: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i ssl_merchan t_id=(withheld)ssl_user_id=(withheld)ssl_pin=(withheld)ssl_transaction _type=ccsale  When I use the following XML style format, shown below, it fails with a "bad request" Code: XML  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i (withheld)(withheld)(withheld)ccsale  Here are my TIdHTTP component settings: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     xHandler.SSLOptions.SSLVersions := [sslvTLSv1_2];     xHTTP.AllowCookies := True;    xHTTP.Request.BasicAuthentication := False;     xHTTP.Request.ContentType := 'application/x-www-form- urlencoded';  //  <-- This ContentType is required by Elavon, shd it be different for PHP (XML style) content?    xHTTP.Request.Accept := '*/*';     xHTTP.HTTPOptions := xHTTP.HTTPOptions + [hoForceEncodeParams, hoKeepOrigProtocol];    xHTTP.ProtocolVersion := pv1_1; Thanks in advance for any help you can provide. [Edited to fix title] ---------------------------------------------------------------------- lucamar · 2021-05-28 21:41 It has nothing to do, I think, with the language you use; the point is that you must send an HTTP request in the format expected by the server, whether your program is in PHP, Pascal, or whatever. Of course, the request message must be correct HTTP, so if you're sending an XML document you can't tell the server your request is "form-URL-encoded", because it isn't. What I don't know off the top of my head is what type you should use for XML, sorry Maybe they can tell it to you? Or it they have some kind of "integration" library, check it and see how they are doing it ... ---------------------------------------------------------------------- sstvmaster · 2021-05-28 21:47 Maybe: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i xHTTP.Request.ContentType := 'application/xml; charset="utf-8"'; ---------------------------------------------------------------------- Gustavo 'Gus' Carreno · 2021-05-29 01:02 Hi RedOctober, First of all, can I ask you to make sense of the mess that you got yourself into, please? Lemme ask you some questions and maybe we can get this sorted out: First of all, if you have it working with form encoded, why are you trying to do it another way? Second would be, why are you trying to send XML when you declare that you're sending form encoded? And third, why are you sending badly formed XML and expecting any positive results? If you can answer me some of these questions maybe we can help you progress further. Cheers, Gus ---------------------------------------------------------------------- RedOctober · 2021-05-29 01:33 Hi Gus, yes, you may ask.  Here are the answers: Background: My goal is to use Elavon's Hosted Payments Page (HPP) to take payments from customers on-line.  They give instructions how to do that in the following links.  All of which I am told, are PHP or CURL examples.  They don't support Lazarus, Indy components, Free Pascal etc. (I must be the first to attempt this) So when ever I ask Elavon developer help a question, I'm just a pain in their ass and they usually give me a terse answer just to get rid of me.  That is why I am turning to this forum for help. https://developer.elavon.com/na/docs/converge/1.0.0/integration- guide/transaction_types/credit_card/sale https://developer.elavon.com/na/docs/converge/1.0.0/integration- guide/integration_methods/hosted_payments https://developer.elavon.com/na/docs/converge/1.0.0/test-cards https://developer.elavon.com/na/docs/converge/1.0.0/overview The first step, is to obtain a token from a "get token" URL, then, I'm to use that token in a redirect of the customer's browser to a different URL.  I was able to obtain a token, after many weeks of frustration, using Postman as an intermediary tool.  Just obtaining a token and redirecting, ends up sending the customer to a blank Hosted Payments Page, where the customer is able to enter their own product description, (even though they will be choosing a product from my website, which clearly has a description), what ever price they want (even zero), and fill in what ever tax they want, even zero, then click the pay button. To have "un-editable" fields in the Hosted Payments Page, I was told that I have to pass all product information, in the initial "get token" request, then, when the customer is redirected, the Hosted Payments Page will be populated with all that info (supposedly, I haven't got that far yet).  The format that you have to send the product info info in, in the initial "get token" request,  is shown at the bottom of this page: https://developer.elavon.com/na/docs/converge/1.0.0/integration- guide/transaction_types/credit_card/sale You see that the format is not form encoded key,value pairs.  It looks like XML, but it's not that either. I think the easiest way to solve this, is if an experienced TIdHTTP programmer wd obtain a developer account with Elavon and tinker with it.  I'm very close to being a beginner with TIdHTTP, even though I've built API interfaces for RingCentral and Data 24/7 and CDYNE.  PayPal "buy now" buttons are a breeze compared to Elavon. So, to answer your questions specifically: QuoteFirst of all, if you have it working with form encoded, why are you trying to do it another way? Because the Elavon dev help personel and Elavon examples require two different formats for data submission.  I can get one to work (get token), the other I can't (submit product info). QuoteSecond would be, why are you trying to send XML when you declare that you're sending form encoded?and QuoteAnd third, why are you sending badly formed XML and expecting any positive results? Bc I'm a dum-ass beginner throwing myself at the mercy of Lazarus gurus for help. I was also told to send the body in Raw format, what ever that is. So, again, form-encoded versus (something else).  The Elavon help group is of no help to us Lazarus devs. Thanks for any help you can provide Gus. ---------------------------------------------------------------------- Gustavo 'Gus' Carreno · 2021-05-29 01:57 Hi RedOctober, Quote from: RedOctober on May 29, 2021, 03:33:53 amHi Gus, yes, you may ask.  Here are the answers: Ahhh, now that's more like it, some docs and some more info for us to bite into. I've had a diagonal look at the docs you gave and I know how I can give you some examples using TidHTTP. Unfortunately, for me, I decided to have a quick query on the forum right at the end of my fuse. I should've been more wise and just gotten to bed, instead of opening a site that makes you think... I'll resume this tomorrow. In the meantime, and with the trove of info you gave, maybe someone in a diff time zone can do it before I wake up. If not, you got my promise that I'll whip something up right after I wake up. Heading to bed and I'll resume this after I've had some beauty sleep. Cheers, Gus ---------------------------------------------------------------------- nanobit · 2021-05-29 05:39 Quote from: RedOctober on May 28, 2021, 11:22:05 pmHow do I send a Request in PHP format? Normally, certain urls (like your buy page) on your webserver are php files (scripts) which show html to your visitors and send http requests to the payment processor. In the simplest case there is no need for own programs (binaries) on the web server. But if allowed on your webserver, exec() can be called from php script to run programs (possibly for sending emails (confirmation, license key) to your customer). Note: My comment was under this assumption: If payment gateways provide the SDK in other languages (PHP, ...), then it should be faster to use their SDK than porting their SDK to Pascal and using an own web server app (cgi) although this is possible as well. ---------------------------------------------------------------------- RedOctober · 2021-05-29 14:18 Hi everyone working on a solution for this issue: My web server is a stand alone Windows .exe, Brook 5 Framework, built using Lazarus 2.0.10 and Indy components to talk to external APIs.  My web server will show product info to the customer, and the customer will click a "buy now" button to go to the Elavon Hosted Payments Page to take payment. I don't want to be dealing with credit cards on my server. This is the reason I must deal with Elavon. They are our credit card processor company.  So, some how, I must overcome this snag, which, in the end, will be a very simple matter of configuring the IIdHTTP properties correctly, and formatting the request correctly.  That is where I am having trouble. ---------------------------------------------------------------------- Gustavo 'Gus' Carreno · 2021-05-29 14:47 Hey RedOctober, Let me start by telling you that the tech support at Elavon is maybe not that technical in nature but has a few scripts they follow. If you're lucky enough to ask a question on those scripts, you get a canned answer, also on said scripts. What they should've told you is that you need an HTTP Client. A recent name to something that has been rising in use due to REST API's and other web centric services. All top languages now have, at least, one HTTP Client implementation on their rooster, if not many. As is the case, in FPC/Lazarus, there's at least 3 of them, that I know of: TfpHTTPClient from the fcl-web package that needs no external libs: https://wiki.freepascal.org/fphttpclientTidHTTP from the Indy10 internet suiteTHTTPSend from the synapse internet suite It really doesn't matter what language you use, what matters is how you use the HTTP Client. I'm still in the middle of making a small project that will help you in all this. I like to be thorough and give, as complete as I can provide, examples. I'll be using the Indy10 HTTP Client, since it's what you already have on your server. I'll also be using an XML library to deal with the XML de/serializing. Working with XML in an OOP way is less prone to issues than if you would just do it via strings. Please be a bit more patient, and I'll have something for you. Cheers, Gus ---------------------------------------------------------------------- Gustavo 'Gus' Carreno · 2021-05-29 19:12 Hey RedOctober, I've finally completed my example. You'll find it attached. It gives you an idea on how to create the XML you need via the laz2_* libraries that deal with XML in UTF-8 format. It also gives you a way to test back and forth the values you need to send. The Log memo is read-only but the XML Memo can be edited and it's content is what is sent to the URL(Elavon Endpoint) I've tested it with an echo server that I quickly made with PHP: Code: PHP  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i             Test Elavon               
           

Test Elavon

           

Need to post some XML

       
    ' elements within a '' pair:     my_merchant_id     my_user_id     my_pin     false     ccsale     000000000000000     1215     1.00 According to the section Implementation Guidelines on this page https://developer.elavon.com/na/docs/converge/1.0.0/integration- guide/integration_methods/xmlapi, QuoteConverge doesn't expect a fully validated XML document, but an XML-formatted request assigned to URL encoded variables called xmldata. Only the Converge-specific elements for the transaction itself are supported. See the part that I underlined. The language is unclear, as it says 'variables' (plural) but only names one variable 'xmldata'. So try this: use to wrap your elementssubmit the request parameters as form-url-encoding of 'xmldata=...' ---------------------------------------------------------------------- RedOctober · 2021-06-01 23:20 Hi Gus and Peirce.  I have not forgotten about you.  Gus, I tried to POST the XML using your test app.  Their server is still reporting "Bad request". Yet your XML is correctly formed according to the Elavon dev help website. I have informed Elavon help that my tool (Lazarus) is more like PostMan than CURL, and if they could come up with a PostMan example of what is on their dev help page, it would be much easier for me to translate, than me trying to piece together what is supposed to happen from their dev help web pages.  So far, they have not given up on me, and they told me they are working on it. What ever I learn, I will make a post in this forum explaining how to use Lazarus to interact with Elavon's Hosted Payment Page. ---------------------------------------------------------------------- trev · 2021-06-01 23:39 The tool is almost completely irrelevant. The issue is not the tool, but the proper format of the HTTP request to send to the server. This was pointed our earlier. You also said "When I use simple Key=Value pairs in the request body, it works.  But when I use, what I call a "XML" style format in the body, it fails with a 'bad request'." So, one has to ask, why change what works? ---------------------------------------------------------------------- egsuh · 2021-06-02 01:26 As PierceNg wrote, doesn't this need something like following? When I press "submit" it displays error message - Invalid Credentials. I think you may correct it. Quote
Here is the data

Of course you may use following:  Quote    with different action address of the form. BTW, how did you setup Brook 5 ? ---------------------------------------------------------------------- Gustavo 'Gus' Carreno · 2021-06-02 01:35 Hey RedOctober, After what @PierceNg said I've added a button to send it with Content Type as application/x-www-form-urlencoded with variable xmldata. From what @egsuh wrote, looks like it's the correct way and it just needs the correct values inputted. I also realized that I had a typo on the root element's name, so I corrected that. Have a go at this version, please. Cheers, Gus ---------------------------------------------------------------------- RedOctober · 2021-06-02 17:19 Quote from: trev on June 02, 2021, 01:39:19 amThe tool is almost completely irrelevant. The issue is not the tool, but the proper format of the HTTP request to send to the server. This was pointed our earlier. You also said "When I use simple Key=Value pairs in the request body, it works.  But when I use, what I call a "XML" style format in the body, it fails with a 'bad request'." So, one has to ask, why change what works? In this case, the tool has to be Lazarus and Free Pascal, and an HTTP component of some kind.  I don't want to use any other language (like Java, PHP, CURL, etc.)  I take your point though, that the Elavon server shd not care what tool was used to issue the request, as long as it is formatted properly and the correct Content-Type is selected. As to "why change what works?"... the "working" call is only part of an .. at least... two step process.  And even the first, now "working" step is incomplete, and cannot allow the second step to proceed.  So, I have to "fix" the fist step, which.. doesn't work.  I have to hand more info in the first "Get Key" step, which is explained on the Elavon dev help web page... shown in a different format than the other "get key" example.  Elavon dev helps are not that helpful.  The examples are not cohesive. Here is the logic trap: Phase 1:  Can I simply get a key from Elavon? Answer:  Yes, but not in the format explained on the Elavon dev help website.  I had to use Elavon's PostMan "Get Key" example, which is not in the same format at what is shown on the Elavon dev help website, and it doesn't allow any more info to be sent, other than a request for a key. Phase 2:  I need to send much more info in the "Get Key" request sent to Elavon. Answer:  I cannot send more info, in the format shown on the Elavon dev help website.  There is no Postman example of this. Latest update from Elavon... (after my very lengthy email explaining all of this, their dev help (person or team) now understands what I am talking about, and is in the process of creating a PostMan example for me (and you all, once I post it up here).  I will, in the next few days, try out Gus's latest iteration of his Elavon client. Thanks to all for your continued assistance.  We've been dealing with Elavon for many years and found them to be a great company to work with.  This is the first attempt at using their API. ---------------------------------------------------------------------- Gustavo 'Gus' Carreno · 2021-06-03 11:11 Hey all, This is a general appraisal, and a very biased one, of the Elavon thingamajig based on the information provided by Red October's recount of support interactions and the very limited Docs I read: Their Web API has no basis on any standard present or past.Their documentation sucks balls, hairy ones.What they call an API I call a very bad catch up on technological debt.I could continue but I think you get my point. Cheers, Gus PS: I needed to vent this. I apologise for the NSFW language and attitude. ---------------------------------------------------------------------- RedOctober · 2021-06-03 12:56 BHAW HAW Ha ha ha hah..  Gus says what we were all thinking.  Aaanywaaaayyy.... If Elavon help can deliver me some working PostMan scripts... I will forgive them. Quote from: Gustavo 'Gus' Carreno on June 03, 2021, 01:11:10 pmHey all, This is a general appraisal, and a very biased one, of the Elavon thingamajig based on the information provided by Red October's recount of support interactions and the very limited Docs I read: Their Web API has no basis on any standard present or past.Their documentation sucks balls, hairy ones.What they call an API I call a very bad catch up on technological debt.I could continue but I think you get my point. Cheers, Gus PS: I needed to vent this. I apologise for the NSFW language and attitude. ---------------------------------------------------------------------- RedOctober · 2021-06-09 19:21 Hi All.  I now have a working PostMan script that calls the Elavon server, supplies to the Elavon server all the info I need sent, then gets the generated token back from the Elavon server.  Now the problem I have is... translating the PostMan script to a TIdHTTP.Request.  I keep getting "Bad Request" using Gus's example .exe above.  Notice that there are THREE Content-Types involved.. Raw, XML and Form encoded.  I will attach a screen shot of the headers and body so you can see.  Here is the PostMan script I need translated.  Error message I am getting: [Debugger Exception Notification] Project elavon_client raised exception class 'EIdHTTPProtocolException' with message: HTTP/1.0 400 Bad Request  In file 'Protocols\IdHTTP.pas' at line 2840: raise EIdHTTPProtocolException.CreateError(LResponseCode, Response.ResponseText, [Ignore this exception type] [Break] [Continue] Thanks for any help you can provide. Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {        "info": {                "_postman_id": "7981dc6b-d702-42e3-bd1f-1c33a458dd7b",                "name": "ccSaleEmptyExample",                "schema": "https://schema.getpost man.com/json/collection/v2.1.0/collection.json"        },        "item": [                {                        "name": "ccsale Copy",                        "request": {                              "auth": {                                        "type": "noauth"                              },                                "method": "POST",                                "header": [                                      {                                              "key": "Content-Type",                                              "name": "Content-Type",                                              "value": "application/x-www-form-urlencoded",                                              "type": "text"                                      },                                ],                              "body": {                                        "mode": "raw",                                        "raw": "xmldata=\n\t withheld \n\t withheld \n\t withheld \n\tccsale
\n\t4000000000000002< /ssl_card_number>\n\t1222\n\t 1.00\n\t1\n\t123\n\t",                                      "options": {                                              "raw": {                                                      "language": "xml"                                                }                                      }                                },                                "url": {                                      "raw": "https://api.demo.convergepay.com/VirtualMerchantDemo/processxml.do",                                        "protocol": "https",                                      "host": [                                              "api",                                                "demo",                                                "convergepay",                                                "com"                                      ],                                        "path": [                                                "VirtualMerchantDemo",                                                "processxml.do"                                        ]                              }                        },                        "response": []                }        ]} ---------------------------------------------------------------------- engkin · 2021-06-10 04:35 Debugging the request based on egsuh's post in reply #14, and reducing it to the minimum accepted version by the server: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i POST /VirtualMerchantDemo/processxml.do HTTP/1.1Host: api.demo.convergepay.comAccept: application/xmlContent-Type: application/x-www-form-urlencodedContent-Length: 347   my_merchant_id  my_user_id  my_pin  false  ccsale  000000000000000  1215  1.00 It needs "Accept: application/xml" and "Content-Type: application/x-www-form-urlencoded". Changing the code in Gus' app in the "Send Raw" button to something along these lines: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TfrmMain.actFileSendRawExecute(Sender: TObject);...    //http.Request.ContentType:='application/xml';    http.Request.ContentType:='application/x-www-form-urlencoded';    http.Request.Accept:='application/xml';... should make it work (did not test it). The response from the server is: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i HTTP/1.1 200 OKServer: ServerX-Frame-Options: SAMEORIGINStrict-Transport- Security: max-age=31536000; includeSubDomains; preloadExpires: 0Cache- Control: no-store, no-cache, must-revalidate, post-check=0, pre- check=0Pragma: no-cacheX-Frame-Options: SAMEORIGINAuthErrorNb: 4025Content-Security-Policy: frame-ancestors 'self'Content- Disposition: inline; filename=response.xmlCPID: AD4-670d2032-7a93-4db0-a3fa-60f9b806b7b8Content-Type: text/xmlAuthError: trueContent-Length: 214Date: Thu, 10 Jun 2021 04:33:11 GMTConnection: keep-aliveSet-Cookie: JSESSIONID=h5ugkZCNXopMZswRjexQOtEE1ZXO42nFlMdiKO2D.server; path=/VirtualMerchantDemo; secure; HttpOnlyDebug-XFF-Seen-by-Edge: 206.189.xxx.xxxClient-IP-Seen-by-Edge: 206.189.xxx.xxx 4025Invalid CredentialsThe credentials supplied in the authorization request are invalid. As you can see, "Invalid Credentials" For similar problems you can use an online service like ReqBin to see request, response and headers. ---------------------------------------------------------------------- RedOctober · 2021-07-21 14:46 I've found that Elavon is a good company to work with for credit card processing.  Their point of sale equipment and computer system is way more reliable than Moneris.  Moneris was complete garbage and left us stranded about once every two weeks. My first exposure to on-line payments was using PayPal's "buy now" buttons, which are really easy to use, self contained, and don't require any programming at all.  It took me only about 20 minutes to set up a Buy Now button. I did not realize that by using PayPal first, the ease-of-use, convenience, and straight forwardness provided by PayPal, actually poisoned my mind when approaching Elavon's API.  I found using the Elavon API a month's long struggle, bc I kept thinking it shd be as easy as PayPal. That was the wrong attitude. Eventually, I just gave up on the XML interaction with Elavon and used their GetToken and Hosted Payments Page  (HPP) combination which itself took weeks to decipher how to use.  I was about to try Square, but just at that moment, I got the Elavon HPP working so I did not pursue the Square attempt any further.  This may not answer your question, but maybe gives you a bit of insight and preparation if you want to use Elavon. The reason I started with Elavon is bc our company already uses them as a CC processing company, so it simplifies our accounting processes to use the same company.