I have an XML webservice URL that I do cURL requests to it.
My cURL request works fine but sometimes it is sent twice resulting in the user being charged twice.
So basically I have a form with an amount and the user clicks on submit to validate the payment. The form code is below.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>Payment Page</title>
<link rel="stylesheet" href="css/spin.css">
<script src="js/jquery-3.3.1.min.js"></script>
<script>
$(document).ready(function () {
$('#submit_btn_echeck').click(function () {
$('#cover-spin').show();
setTimeout(function () {
$('#cover-spin').hide();
}, 6000);
});
});
</script>
</head>
<body id="main_body" class="no_guidelines">
<div id="form_container" class="StandShadow SLarge">
<form action="pay_echeck/retained" method="post">
<input type="hidden" name="csrf_token_name" value="8825288f44db225ff01b86d3ec8c166c" />
<input type="hidden" name="amount_to_pay" value="119.62" />
<div class="form_description">
<h2>Payment Information</h2>
<p>Please review the details below before submitting your payment.</p>
</div>
<ul class="payment_summary">
<li class="payment_summary_amount">
<table>
<tr>
<td style="float:right;border-bottom:1px dashed #ccc;">Charge Amount: </td>
<td style="border-left:1px dashed #ccc;border-bottom:1px dashed #ccc;"><span style="float:right;">$119.62</span></td>
</tr>
<tr>
<td style="float:right;"><b>TOTAL: </b></td>
<td style="border-left:1px dashed #ccc;"><span style="float:right;"><b id="totalCC"> $119.62</b></span></td>
</tr>
</table>
</li>
</ul>
<input id="submit_btn_echeck" class="button_text btn_primary" type="submit" value="Submit" />
<div id="cover-spin"></div>
</form>
</div>
</body>
</html>
When the user submits the form I take the value of the hidden input named amount_to_pay and send it to the web-service via PHP cURL using the code below.
ob_start();
$out = fopen('curl_log_'.date('mdY_his').'.txt', 'w');
$ch = curl_init();
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_STDERR, $out);
curl_setopt($ch, CURLOPT_URL, "https://mywebserviceurl.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 60000);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);//data sent as PHP XML object
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_CAINFO, __DIR__."/cacert.pem");
curl_setopt($ch, CURLOPT_CAPATH, __DIR__."/cacert.pem");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: text/xml;charset=UTF-8', 'SoapAction; *']);
$content = trim(curl_exec($ch));
curl_close($ch);
$debug = ob_get_clean();
fwrite($out,$debug);
fwrite($out,$content);
fclose($out);
if (isset($content)) {
return $content;
}
Everything works fine, some users pay with no issues but some others get charged twice and when I try to debug the curl request sent and the response received for one of those users for example I see that cURL request was sent twice. Below is a debug of the Curl request that was sent twice:
First request sent
* Trying xx.xx.xxx.xx:443...
* TCP_NODELAY set
* Connected to webservice.com (xx.xx.xx.xx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: .../application/libraries/cacert.pem
CApath: .../application/libraries/cacert.pem
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: OU=Domain Control Validated; CN=*.webservice.com
* start date: Sep 13 15:44:59 2019 GMT
* expire date: Sep 13 15:44:59 2020 GMT
* subjectAltName: host "webservice.com" matched cert's "webservice.com"
* issuer: C=BE; O=GlobalSign nv-sa; CN=AlphaSSL CA - SHA256 - G2
* SSL certificate verify ok.
> POST /ws/webservice_svc.cfc?wsdl HTTP/1.1
Host: webservice.com
Accept: */*
Content-Type: text/xml;charset=UTF-8
SoapAction: *
Content-Length: 939
* upload completely sent off: 939 out of 939 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 06 Oct 2019 22:59:33 GMT
< Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/5.4.16 mod_jk/1.2.41
< Strict-Transport-Security: max-age=15768000
< Set-Cookie: CFID=2685691; Expires=Mon, 07-Oct-2019 **08:59:33 GMT**; Path=/; Secure; HttpOnly
< Set-Cookie: CFTOKEN=d3c5d1e31b91dd7d-60FEB753-CF6E-1A16-AB0F8681EB1809A6; Expires=Mon, 07-Oct-2019 08:59:33 GMT; Path=/; Secure; HttpOnly
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: ALLOW-FROM https://foundationtitlemelbourne.com/
< Referrer-Policy: same-origin
< Transfer-Encoding: chunked
< Content-Type: text/xml;charset=utf-8
<
* Connection #0 to host webservice.com left intact
<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:PayRPOResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://ws">
<PayRPOReturn xsi:type="xsd:string"><result>Successful, 3990631</result></PayRPOReturn>
</ns1:PayRPOResponse>
</soapenv:Body>
</soapenv:Envelope>
Second same request sent, the only difference I see is that it was sent 8 seconds after the first one above
* Trying xx.xx.xxx.xx:443...
* TCP_NODELAY set
* Connected to webservice.com (xx.xx.xx.xx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: .../application/libraries/cacert.pem
CApath: .../application/libraries/cacert.pem
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: OU=Domain Control Validated; CN=*.webservice.com
* start date: Sep 13 15:44:59 2019 GMT
* expire date: Sep 13 15:44:59 2020 GMT
* subjectAltName: host "webservice.com" matched cert's "webservice.com"
* issuer: C=BE; O=GlobalSign nv-sa; CN=AlphaSSL CA - SHA256 - G2
* SSL certificate verify ok.
> POST /ws/webservice_svc.cfc?wsdl HTTP/1.1
Host: webservice.com
Accept: */*
Content-Type: text/xml;charset=UTF-8
SoapAction: *
Content-Length: 939
* upload completely sent off: 939 out of 939 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 06 Oct 2019 22:59:33 GMT
< Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/5.4.16 mod_jk/1.2.41
< Strict-Transport-Security: max-age=15768000
< Set-Cookie: CFID=2685691; Expires=Mon, 07-Oct-2019 **08:59:41 GMT**; Path=/; Secure; HttpOnly
< Set-Cookie: CFTOKEN=d3c5d1e31b91dd7d-60FEB753-CF6E-1A16-AB0F8681EB1809A6; Expires=Mon, 07-Oct-2019 08:59:33 GMT; Path=/; Secure; HttpOnly
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: ALLOW-FROM https://foundationtitlemelbourne.com/
< Referrer-Policy: same-origin
< Transfer-Encoding: chunked
< Content-Type: text/xml;charset=utf-8
<
* Connection #0 to host webservice.com left intact
<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:PayRPOResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://ws">
<PayRPOReturn xsi:type="xsd:string"><result>Successful, 3990631</result></PayRPOReturn>
</ns1:PayRPOResponse>
</soapenv:Body>
</soapenv:Envelope>
Any lead or help please on what could be happening would be very appreciated.
Thanks.
2
Answers
Sometimes the payment gateway can respond twice for one payment request.
It is normal to happen, because the server which sends the response can be down for a period of time.
In this case the payment system tries to send the respond once, or multiple times.
It is your duty as a developer to treat this exception.
You can do it based on the id of the transaction send/retunred from the payment system.
I think the problem lies here:
You are showing the submit button again after 6 seconds, so the user is likely to click it again, because it would seem as if the payment did not work the first time.
I would keep the
#cover_spin
visible, and let it disappear when the page navigates to the success or failure page.I’d also disable the submit button after it is clicked for extra measure.
$('#submit_btn_echeck').prop('disabled', true);