Open fureszpeter opened 10 years ago
I dont understand what fix you suggest?
Ok, I have found more things, and try to collect.
1.--------------ORIGINAL-------------- In the original code, we select the transaction where the status is "new" OR "in process". Even if we found or not any row, in the next step the code update the status of the transaction to "in process", even if it is already processed. So I suggest to put the update section after the IF condition.
// Status - New 0 ; Proceed 1 ; In Process 2 $QUERY = "SELECT id, cardid, amount, vat, paymentmethod, cc_owner, cc_number, cc_expires, creationdate, status, cvv, credit_card_type, currency, item_id, item_type " . " FROM cc_epayment_log " . " WHERE id = ".$transactionID." AND (status = 0 OR (status = 2 AND $NOW_2MIN))"; $transaction_data = $paymentTable->SQLExec ($DBHandle_max, $QUERY);
$item_id = $transaction_data[0][13]; $amount = $transaction_data[0][2]; $item_type = $transaction_data[0][14];
//Update the Transaction Status to 1 $QUERY = "UPDATE cc_epayment_log SET status = 2 WHERE id = ".$transactionID; write_log(LOGFILE_EPAYMENT, basename(FILE).' line:'.LINE."- QUERY = $QUERY"); $paymentTable->SQLExec ($DBHandle_max, $QUERY);
if (!is_array($transaction_data) && count($transaction_data) == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE). ' line:'.LINE."- transactionID=$transactionID"." ERROR INVALID TRANSACTION ID PROVIDED, TRANSACTION ID =".$transactionID); exit(); } else { write_log(LOGFILE_EPAYMENT, basename(FILE). ' line:'.LINE."- transactionID=$transactionID"." EPAYMENT RESPONSE: TRANSACTIONID = ".$transactionID. " FROM ".$transaction_data[0][4]."; FOR CUSTOMER ID ".$transaction_data[0][1]."; OF AMOUNT ".$transaction_data[0][2]); }
So the code will looks like this
// Status - New 0 ; Proceed 1 ; In Process 2 $QUERY = "SELECT id, cardid, amount, vat, paymentmethod, cc_owner, cc_number, cc_expires, creationdate, status, cvv, credit_card_type, currency, item_id, item_type " . " FROM cc_epayment_log " . " WHERE id = " . $transactionID . " AND (status = 0 OR (status = 2 AND $NOW_2MIN))"; $transaction_data = $paymentTable->SQLExec($DBHandle_max, $QUERY);
$item_id = $transaction_data[0][13]; $amount = $transaction_data[0][2]; $item_type = $transaction_data[0][14];
if (!is_array($transaction_data) && count($transaction_data) == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " ERROR INVALID TRANSACTION ID PROVIDED, TRANSACTION ID =" . $transactionID); exit(); } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " EPAYMENT RESPONSE: TRANSACTIONID = " . $transactionID . " FROM " . $transaction_data[0][4] . "; FOR CUSTOMER ID " . $transaction_data[0][1] . "; OF AMOUNT " . $transaction_data[0][2]); }
//Update the Transaction Status to 1 $QUERY = "UPDATE cc_epayment_log SET status = 2 WHERE id = " . $transactionID; write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- QUERY = $QUERY"); $paymentTable->SQLExec($DBHandle_max, $QUERY);
if (!is_array($transaction_data) && count($transaction_data) == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " ERROR INVALID TRANSACTION ID PROVIDED, TRANSACTION ID =" . $transactionID); exit(); } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " EPAYMENT RESPONSE: TRANSACTIONID = " . $transactionID . " FROM " . $transaction_data[0][4] . "; FOR CUSTOMER ID " . $transaction_data[0][1] . "; OF AMOUNT " . $transaction_data[0][2]); }
if (!is_array($transaction_data) || (is_array($transaction_data) && count($transaction_data)) == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " ERROR INVALID TRANSACTION ID PROVIDED, TRANSACTION ID =" . $transactionID); exit(); } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " EPAYMENT RESPONSE: TRANSACTIONID = " . $transactionID . " FROM " . $transaction_data[0][4] . "; FOR CUSTOMER ID " . $transaction_data[0][1] . "; OF AMOUNT " . $transaction_data[0][2]); }
I realized from the checkout log, that the checkout_process script execution time takes 3-4 minutes. This is the source of the duplication bug. For example the first thread start the execution at 14:00:00 and stops at 14:03:00. Read the data from the database, the transaction status, etc... The second thread start the execution at 14:01:00 and ending at 14:04:00 . Because the first thread is still running, and it is still not updated the status of the transaction to 1, the second thread read the status and think that it is in "in process" status. When thread 1 finishing, creating the invoice, add the amount to the user, and set the status to "proceed". The second thread do it again, create invoice, add the amount to the card and so on. Each thread means duplication.
ORIGINAL CODE:
// Headers PayPal system to validate $header .= "POST /cgi-bin/webscr HTTP/1.1\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Host: www.paypal.com\r\n"; $header .= "Content-Length: " . strlen($req) . "\r\n\r\n"; for ($i = 1; $i <= 3; $i++) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-OPENDING HTTP CONNECTION TO " . PAYPAL_VERIFY_URL); $fp = fsockopen(PAYPAL_VERIFY_URL, 443, $errno, $errstr, 30); if ($fp) { break; } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . " -Try#" . $i . " Failed to open HTTP Connection : " . $errstr . ". Error Code: " . $errno); sleep(3); } } if (!$fp) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-Failed to open HTTP Connection: " . $errstr . ". Error Code: " . $errno); exit(); } else { fputs($fp, $header . $req); $flag_ver = False; while (!feof($fp)) { $res = fgets($fp, 1024); $gather_res .= $res; $res = trim($res);
MODIFIED CODE:
In the VERIFIED condition check, just put the break; . After that the long execution time gone.
// Headers PayPal system to validate $header .= "POST /cgi-bin/webscr HTTP/1.1\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Host: www.paypal.com\r\n"; $header .= "Content-Length: " . strlen($req) . "\r\n\r\n"; for ($i = 1; $i <= 3; $i++) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-OPENDING HTTP CONNECTION TO " . PAYPAL_VERIFY_URL); $fp = fsockopen(PAYPAL_VERIFY_URL, 443, $errno, $errstr, 30); if ($fp) { break; } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . " -Try#" . $i . " Failed to open HTTP Connection : " . $errstr . ". Error Code: " . $errno); sleep(3); } } if (!$fp) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-Failed to open HTTP Connection: " . $errstr . ". Error Code: " . $errno); exit(); } else { fputs($fp, $header . $req); $flag_ver = False; while (!feof($fp)) { $res = fgets($fp, 1024); $gather_res .= $res; $res = trim($res); if (strcmp($res, "VERIFIED") == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-PAYPAL Transaction Verification Status: Verified "); $flag_ver = True; * break;* } } if (!$flag_ver) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-PAYPAL Transaction Verification Status: Failed \nreq=$req\n$gather_res"); $security_verify = false; } } fclose($fp);
Finally I put the whole code in a transaction, so something went wrong, just ROLLBACK everything.
2014/1/7 Areski Belaid notifications@github.com
I dont understand what fix you suggest?
Reply to this email directly or view it on GitHubhttps://github.com/Star2Billing/a2billing/issues/73#issuecomment-31730887 .
Could you send a patch so we can see the diff? The best way might actually to fork a2billing on github, apply your changes and send a pull request. https://help.github.com/articles/creating-a-pull-request
On Tue, Jan 7, 2014 at 5:42 PM, fureszpeter notifications@github.comwrote:
Ok, I have found more things, and try to collect.
1.--------------ORIGINAL-------------- In the original code, we select the transaction where the status is "new" OR "in process". Even if we found or not any row, in the next step the code update the status of the transaction to "in process", even if it is already processed. So I suggest to put the update section after the IF condition.
// Status - New 0 ; Proceed 1 ; In Process 2 $QUERY = "SELECT id, cardid, amount, vat, paymentmethod, cc_owner, cc_number, cc_expires, creationdate, status, cvv, credit_card_type, currency, item_id, item_type " . " FROM cc_epayment_log " . " WHERE id = ".$transactionID." AND (status = 0 OR (status = 2 AND $NOW_2MIN))"; $transaction_data = $paymentTable->SQLExec ($DBHandle_max, $QUERY);
$item_id = $transaction_data[0][13]; $amount = $transaction_data[0][2]; $item_type = $transaction_data[0][14];
//Update the Transaction Status to 1 $QUERY = "UPDATE cc_epayment_log SET status = 2 WHERE id = ".$transactionID;
write_log(LOGFILE_EPAYMENT, basename(FILE).' line:'.LINE."- QUERY
$QUERY"); $paymentTable->SQLExec ($DBHandle_max, $QUERY);
if (!is_array($transaction_data) && count($transaction_data) == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE). ' line:'.LINE."- transactionID=$transactionID"." ERROR INVALID TRANSACTION ID PROVIDED, TRANSACTION ID =".$transactionID); exit(); } else { write_log(LOGFILE_EPAYMENT, basename(FILE). ' line:'.LINE."- transactionID=$transactionID"." EPAYMENT RESPONSE: TRANSACTIONID = ".$transactionID. " FROM ".$transaction_data[0][4]."; FOR CUSTOMER ID ".$transaction_data[0][1]."; OF AMOUNT ".$transaction_data[0][2]); }
- Update after the if condition____
So the code will looks like this
// Status - New 0 ; Proceed 1 ; In Process 2 $QUERY = "SELECT id, cardid, amount, vat, paymentmethod, cc_owner, cc_number, cc_expires, creationdate, status, cvv, credit_card_type, currency, item_id, item_type " . " FROM cc_epayment_log " . " WHERE id = " . $transactionID . " AND (status = 0 OR (status = 2 AND $NOW_2MIN))"; $transaction_data = $paymentTable->SQLExec($DBHandle_max, $QUERY);
$item_id = $transaction_data[0][13]; $amount = $transaction_data[0][2]; $item_type = $transaction_data[0][14];
if (!is_array($transaction_data) && count($transaction_data) == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " ERROR INVALID TRANSACTION ID PROVIDED, TRANSACTION ID =" . $transactionID); exit(); } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " EPAYMENT RESPONSE: TRANSACTIONID = " . $transactionID . " FROM " . $transaction_data[0][4] . "; FOR CUSTOMER ID " . $transaction_data[0][1] . "; OF AMOUNT " . $transaction_data[0][2]); }
//Update the Transaction Status to 1 $QUERY = "UPDATE cc_epayment_log SET status = 2 WHERE id = " . $transactionID; write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- QUERY = $QUERY"); $paymentTable->SQLExec($DBHandle_max, $QUERY);
- -----------------WRONG IF CONDITION-------------------- The following condition check is not working as expected. We exit if the result is not an array AND the array count is 0. It will never TRUE! If it is not an array, the count will never 0. . We want to exit, but the if condition will go to the else section, because is_array($transaction_data) is TRUE.
if (!is_array($transaction_data) && count($transaction_data) == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " ERROR INVALID TRANSACTION ID PROVIDED, TRANSACTION ID =" . $transactionID); exit(); } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " EPAYMENT RESPONSE: TRANSACTIONID = " . $transactionID . " FROM " . $transaction_data[0][4] . "; FOR CUSTOMER ID " . $transaction_data[0][1] . "; OF AMOUNT " . $transaction_data[0][2]); }
- _____ MODIFIED IF CONDITION__ We want to exit, if the result is not an array, or it is an array BUT it is empty
if (!is_array($transaction_data) || (is_array($transaction_data) && count($transaction_data)) == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " ERROR INVALID TRANSACTION ID PROVIDED, TRANSACTION ID =" . $transactionID); exit(); } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "- transactionID=$transactionID" . " EPAYMENT RESPONSE: TRANSACTIONID = " . $transactionID . " FROM " . $transaction_data[0][4] . "; FOR CUSTOMER ID " . $transaction_data[0][1] . "; OF AMOUNT " . $transaction_data[0][2]); }
- _____ Long-Long script execution time, is the source of all bug
I realized from the checkout log, that the checkout_process script execution time takes 3-4 minutes. This is the source of the duplication bug. For example the first thread start the execution at 14:00:00 and stops at 14:03:00. Read the data from the database, the transaction status, etc... The second thread start the execution at 14:01:00 and ending at 14:04:00 . Because the first thread is still running, and it is still not updated the status of the transaction to 1, the second thread read the status and think that it is in "in process" status. When thread 1 finishing, creating the invoice, add the amount to the user, and set the status to "proceed". The second thread do it again, create invoice, add the amount to the card and so on. Each thread means duplication.
ORIGINAL CODE:
// Headers PayPal system to validate $header .= "POST /cgi-bin/webscr HTTP/1.1\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Host: www.paypal.com\r\n"; $header .= "Content-Length: " . strlen($req) . "\r\n\r\n"; for ($i = 1; $i <= 3; $i++) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-OPENDING HTTP CONNECTION TO " . PAYPAL_VERIFY_URL); $fp = fsockopen(PAYPAL_VERIFY_URL, 443, $errno, $errstr, 30); if ($fp) { break; } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . " -Try#" . $i . " Failed to open HTTP Connection : " . $errstr . ". Error Code: " . $errno); sleep(3); } } if (!$fp) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-Failed to open HTTP Connection: " . $errstr . ". Error Code: " . $errno); exit(); } else { fputs($fp, $header . $req); $flag_ver = False; while (!feof($fp)) { $res = fgets($fp, 1024); $gather_res .= $res; $res = trim($res);
- if (strcmp($res, "VERIFIED") == 0) {*
- write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-PAYPAL Transaction Verification Status: Verified ");*
- $flag_ver = True;*
- }* } if (!$flag_ver) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-PAYPAL Transaction Verification Status: Failed \nreq=$req\n$gather_res"); $security_verify = false; } } fclose($fp);
MODIFIED CODE:
In the VERIFIED condition check, just put the break; . After that the long execution time gone.
// Headers PayPal system to validate $header .= "POST /cgi-bin/webscr HTTP/1.1\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Host: www.paypal.com\r\n"; $header .= "Content-Length: " . strlen($req) . "\r\n\r\n"; for ($i = 1; $i <= 3; $i++) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-OPENDING HTTP CONNECTION TO " . PAYPAL_VERIFY_URL); $fp = fsockopen(PAYPAL_VERIFY_URL, 443, $errno, $errstr, 30); if ($fp) { break; } else { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . " -Try#" . $i . " Failed to open HTTP Connection : " . $errstr . ". Error Code: " . $errno); sleep(3); } } if (!$fp) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-Failed to open HTTP Connection: " . $errstr . ". Error Code: " . $errno); exit(); } else { fputs($fp, $header . $req); $flag_ver = False; while (!feof($fp)) { $res = fgets($fp, 1024); $gather_res .= $res; $res = trim($res); if (strcmp($res, "VERIFIED") == 0) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-PAYPAL Transaction Verification Status: Verified "); $flag_ver = True;
- break;* } } if (!$flag_ver) { write_log(LOGFILE_EPAYMENT, basename(FILE) . ' line:' . LINE . "-PAYPAL Transaction Verification Status: Failed \nreq=$req\n$gather_res"); $security_verify = false; } } fclose($fp);
- TRANSACTION
Finally I put the whole code in a transaction, so something went wrong, just ROLLBACK everything.
2014/1/7 Areski Belaid notifications@github.com
I dont understand what fix you suggest?
Reply to this email directly or view it on GitHub< https://github.com/Star2Billing/a2billing/issues/73#issuecomment-31730887>
.
Reply to this email directly or view it on GitHubhttps://github.com/Star2Billing/a2billing/issues/73#issuecomment-31754181 .
Kind regards, /Areski
Arezqui Belaid, areski@gmail.com Founder at Star2Billing (www.star2billing.com)
Tel: +34650784355 Twitter: http://twitter.com/areskib LinkedIn: http://www.linkedin.com/in/areski
Hello, did you get it fixed ? is it possible to get the whole working code. thanks.
When PayPal IPN send IPN message, customer balance added every IPN call, each time when IPN notification arrived.
The bug at line 82
//Update the Transaction Status to 1 $QUERY = "UPDATE cc_epayment_log SET status = 2 WHERE id = ".$transactionID;
In the comment, developer want to set the status to 1, but set to 2. When the earlier IPN SET the transaction status to 1, line 82 in the next IPN call SET BACK to status to 2 without any condition checking.
Bug is reproducable