array(), 'v6' => array()); $data = json_decode($json, true); if (!is_array($data) || empty($data['Answer']) || !is_array($data['Answer'])) return $ips; foreach ($data['Answer'] as $ans) { if (empty($ans['data'])) continue; $type = isset($ans['type']) ? intval($ans['type']) : 0; if ($type === 1 || $type === 28) ppAddResolvedIp($ips, $ans['data']); } return $ips; } function ppMergeIps($into, $from) { if (!is_array($from)) return $into; foreach (array('v4', 'v6') as $k) { if (empty($from[$k]) || !is_array($from[$k])) continue; foreach ($from[$k] as $ip) ppAddResolvedIp($into, $ip); } return $into; } function ppCurlGetByIp($url, $headers = array(), $verifyTls = true) { if (!function_exists('curl_init')) return false; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 4); curl_setopt($ch, CURLOPT_TIMEOUT, 6); curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifyTls ? 1 : 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0); $cacert = ppCacertPath(); if ($cacert) curl_setopt($ch, CURLOPT_CAINFO, $cacert); $res = curl_exec($ch); curl_close($ch); return is_string($res) ? $res : false; } // Resolve PayPal hostnames without depending on the web server's DNS. // Shared hosts often have a broken resolver (curl error: Could not resolve host) // even though outbound HTTPS to public IPs still works. function ppResolveHostIps($host) { static $cache = array(); if (isset($cache[$host])) return $cache[$host]; $ips = array('v4' => array(), 'v6' => array()); // Bypass the local resolver first. This host already fails to resolve // ipnpb.paypal.com / www.paypal.com through PHP cURL's DNS. foreach (array('1.1.1.1', '8.8.8.8') as $ns) { $ips = ppMergeIps($ips, ppDnsUdpA($host, $ns)); if (!empty($ips['v4'])) break; } if (empty($ips['v4']) && empty($ips['v6'])) { $dohUrls = array( 'https://1.1.1.1/dns-query?name=' . rawurlencode($host) . '&type=A', 'https://8.8.8.8/resolve?name=' . rawurlencode($host) . '&type=A', ); $dohHeaders = array('Accept: application/dns-json'); foreach (array(true, false) as $verifyTls) { foreach ($dohUrls as $dohUrl) { $json = ppCurlGetByIp($dohUrl, $dohHeaders, $verifyTls); if ($json) $ips = ppMergeIps($ips, ppParseDohAnswers($json)); if (!empty($ips['v4']) || !empty($ips['v6'])) break 2; } } } if (empty($ips['v4']) && empty($ips['v6'])) { if (function_exists('dns_get_record')) { foreach (array(DNS_A, DNS_AAAA) as $type) { $recs = @dns_get_record($host, $type); if (!is_array($recs)) continue; foreach ($recs as $rec) { if (!empty($rec['ip'])) ppAddResolvedIp($ips, $rec['ip']); if (!empty($rec['ipv6'])) ppAddResolvedIp($ips, $rec['ipv6']); } } } $byName = @gethostbyname($host); if (is_string($byName) && $byName !== $host) ppAddResolvedIp($ips, $byName); } $cache[$host] = $ips; return $ips; } function ppDnsSkipName($resp, $offset) { $len = strlen($resp); $jumps = 0; while ($offset < $len && $jumps < 10) { $lab = ord($resp[$offset]); if ($lab === 0) return $offset + 1; if (($lab & 0xC0) === 0xC0) return $offset + 2; $offset += $lab + 1; $jumps++; } return $offset; } function ppDnsUdpA($host, $server, $timeout = 2) { $ips = array('v4' => array(), 'v6' => array()); if (!function_exists('fsockopen')) return $ips; $id = function_exists('random_int') ? random_int(0, 65535) : mt_rand(0, 65535); $qname = ''; foreach (explode('.', $host) as $label) { $qname .= chr(strlen($label)) . $label; } $query = pack('n', $id) . "\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00" . $qname . "\x00" . pack('nn', 1, 1); $errno = 0; $errstr = ''; $fp = @fsockopen('udp://' . $server, 53, $errno, $errstr, $timeout); if (!$fp) return $ips; stream_set_timeout($fp, $timeout); fwrite($fp, $query); $resp = fread($fp, 512); fclose($fp); if (!is_string($resp) || strlen($resp) < 12) return $ips; $header = unpack('nid/nflags/nqdcount/nancount/nnscount/narcount', substr($resp, 0, 12)); if (!$header || $header['id'] !== $id || $header['ancount'] < 1) return $ips; $offset = ppDnsSkipName($resp, 12) + 4; for ($i = 0; $i < $header['ancount'] && $offset + 10 <= strlen($resp); $i++) { $offset = ppDnsSkipName($resp, $offset); if ($offset + 10 > strlen($resp)) break; $rr = unpack('ntype/nclass/Nttl/nrdlength', substr($resp, $offset, 10)); $offset += 10; if (!$rr) break; $rdlen = $rr['rdlength']; if ($offset + $rdlen > strlen($resp)) break; if ($rr['type'] === 1 && $rdlen === 4) { ppAddResolvedIp($ips, implode('.', unpack('C4', substr($resp, $offset, 4)))); } $offset += $rdlen; } return $ips; } function ppApplyIpnCurlOpts($ch, $req) { curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $req); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); if (defined('CURL_SSLVERSION_TLSv1_2')) { curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); } else { curl_setopt($ch, CURLOPT_SSLVERSION, 6); } curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 8); curl_setopt($ch, CURLOPT_TIMEOUT, 20); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'User-Agent: PHP-IPN-VerificationScript', 'Connection: Close', )); $cacert = ppCacertPath(); if ($cacert) curl_setopt($ch, CURLOPT_CAINFO, $cacert); } function ppIpnCurlPost($url, $req, $resolveRules = array(), $dnsServers = '', $ipresolve = 0) { $ch = curl_init($url); ppApplyIpnCurlOpts($ch, $req); if ($resolveRules) curl_setopt($ch, CURLOPT_RESOLVE, $resolveRules); if ($dnsServers !== '' && defined('CURLOPT_DNS_SERVERS')) { @curl_setopt($ch, CURLOPT_DNS_SERVERS, $dnsServers); } if ($ipresolve && defined('CURL_IPRESOLVE_V4')) { curl_setopt($ch, CURLOPT_IPRESOLVE, $ipresolve); } $res = curl_exec($ch); $info = array( 'body' => $res, 'http' => curl_getinfo($ch, CURLINFO_HTTP_CODE), 'error' => ($res === false) ? curl_error($ch) : '', 'primary_ip' => curl_getinfo($ch, CURLINFO_PRIMARY_IP), ); curl_close($ch); return $info; } function ppIpnStreamPost($host, $req, $ip) { $remote = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? '[' . $ip . ']' : $ip; $ssl = array( 'verify_peer' => true, 'verify_peer_name' => true, 'peer_name' => $host, 'SNI_enabled' => true, ); $cacert = ppCacertPath(); if ($cacert) $ssl['cafile'] = $cacert; $errno = 0; $errstr = ''; $fp = @stream_socket_client( 'ssl://' . $remote . ':443', $errno, $errstr, 20, STREAM_CLIENT_CONNECT, stream_context_create(array('ssl' => $ssl)) ); if (!$fp) { return array('body' => false, 'http' => 0, 'error' => "stream $ip: $errno $errstr", 'primary_ip' => $ip); } $out = "POST /cgi-bin/webscr HTTP/1.1\r\n" . "Host: {$host}\r\n" . "User-Agent: PHP-IPN-VerificationScript\r\n" . "Content-Type: application/x-www-form-urlencoded\r\n" . "Content-Length: " . strlen($req) . "\r\n" . "Connection: close\r\n\r\n" . $req; fwrite($fp, $out); stream_set_timeout($fp, 20); $resp = ''; while (!feof($fp)) { $chunk = fread($fp, 8192); if ($chunk === false || $chunk === '') break; $resp .= $chunk; } fclose($fp); $headerEnd = strpos($resp, "\r\n\r\n"); $headers = $headerEnd === false ? $resp : substr($resp, 0, $headerEnd); $body = $headerEnd === false ? '' : substr($resp, $headerEnd + 4); $http = 0; if (preg_match('/HTTP\/\d\.\d\s+(\d+)/', $headers, $m)) $http = intval($m[1]); return array('body' => $body, 'http' => $http, 'error' => '', 'primary_ip' => $ip); } function ppIpnStatusFromBody($res) { if (!is_string($res)) return ''; $body = trim($res); if ($body === 'VERIFIED' || $body === 'INVALID') return $body; if (preg_match('/^(VERIFIED|INVALID)\s*$/m', $body, $m)) return $m[1]; return ''; } function ppFormatIpnAttempt($url, $info, $via) { return $url . " via=" . $via . " ip=" . (isset($info['primary_ip']) ? $info['primary_ip'] : '') . " http_code=" . $info['http'] . " curl_error='" . $info['error'] . "'" . " response=" . var_export($info['body'], true); } // POST the IPN back to PayPal per // https://developer.paypal.com/api/nvp-soap/ipn/IPNImplementation/ // Live: https://ipnpb.paypal.com/cgi-bin/webscr // Sandbox: https://ipnpb.sandbox.paypal.com/cgi-bin/webscr // www.paypal.com/cgi-bin/webscr remains a documented legacy fallback. function ppVerifyIpnWithPaypal($req) { $useSandbox = !empty($_POST['test_ipn']); $endpoints = $useSandbox ? array( 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr', 'https://www.sandbox.paypal.com/cgi-bin/webscr', ) : array( 'https://ipnpb.paypal.com/cgi-bin/webscr', 'https://www.paypal.com/cgi-bin/webscr', ); $attempts = array(); foreach ($endpoints as $url) { $host = parse_url($url, PHP_URL_HOST); $ips = ppResolveHostIps($host); $resolveRules = array(); $addrList = array(); foreach ($ips['v4'] as $ip) $addrList[] = $ip; foreach ($ips['v6'] as $ip) $addrList[] = '[' . $ip . ']'; if ($addrList) $resolveRules[] = $host . ':443:' . implode(',', $addrList); $attempts[] = $host . " resolved=" . ($addrList ? implode(',', $addrList) : '(none)'); $variants = array(); if ($resolveRules) { $variants[] = array('rules' => $resolveRules, 'dns' => '', 'ipresolve' => 0, 'via' => 'curl+resolve'); } else { $variants[] = array('rules' => array(), 'dns' => '', 'ipresolve' => 0, 'via' => 'curl'); $variants[] = array('rules' => array(), 'dns' => '1.1.1.1,8.8.8.8', 'ipresolve' => 0, 'via' => 'curl+dnsservers'); } foreach ($variants as $variant) { $info = ppIpnCurlPost($url, $req, $variant['rules'], $variant['dns'], $variant['ipresolve']); $attempts[] = ppFormatIpnAttempt($url, $info, $variant['via']); $status = ppIpnStatusFromBody($info['body']); if ($status === 'VERIFIED' || $status === 'INVALID') { return array('status' => $status, 'url' => $url, 'attempts' => $attempts); } } $streamIps = array_slice(array_merge($ips['v4'], $ips['v6']), 0, 2); foreach ($streamIps as $ip) { $info = ppIpnStreamPost($host, $req, $ip); $attempts[] = ppFormatIpnAttempt($url, $info, 'stream'); $status = ppIpnStatusFromBody($info['body']); if ($status === 'VERIFIED' || $status === 'INVALID') { return array('status' => $status, 'url' => $url, 'attempts' => $attempts); } } } return array('status' => 'ERROR', 'url' => '', 'attempts' => $attempts); } // Callback name must be a string. A bare constant was tolerated by older // PHP (it fell back to the bareword as a string) but is a fatal Error in PHP 8. ob_start('saveOutput'); date_default_timezone_set("Canada/Pacific"); // output date and POST-ed values echo(date('r') . "\n\n"); $x = $_POST; if (isset($x['callbackPW'])) unset($x['callbackPW']); print_r($x); echo("\n\n"); // Exit if there is no TRXnum if (!array_key_exists('custom',$_POST) || !is_numeric($_POST['custom']) || $_POST['custom'] == 0) { exit(); } // *************** // post back to PayPal system to validate // *************** /* $req = 'cmd=_notify-validate'; foreach ($_POST as $key => $value) { $value = urlencode(stripslashes($value)); $req .= "&$key=$value"; } $header .= "POST /cgi-bin/webscr HTTP/1.1\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Content-Length: " . strlen($req) . "\r\n"; $header .= "Host: www.paypal.com\r\n"; $header .= "Connection: close\r\n"; $header .= "\r\n";*/ // connect to the server /*if (array_key_exists('test_ipn',$_POST)) $server = "www.sandbox.paypal.com"; else $server = "www.paypal.com"; $fp = fsockopen($server, 80, $errno, $errstr, 30); $unexpectedErrors = 0; if ($fp === false) { $errorMessage = "ERROR: Error connecting to PayPal server to validate IPN: $errno - $errstr"; echo("$errorMessage\n"); $unexpectedErrors = 1; } else { // post the data fputs ($fp, $header . $req); while (!feof($fp)) { // get the return status $res = trim(fgets($fp, 1024)); if ($res === "VERIFIED") { echo("VERIFIED"); } elseif ($res === "INVALID") { $errorMessage = "ERROR: The PayPal server could not validate the IPN"; echo("$errorMessage\n"); $unexpectedErrors = 1; } } fclose ($fp); }*/ echo("\n\n"); ################## Start Post Back to PayPal ##################################### $unexpectedErrors = 0; $raw_post_data = file_get_contents('php://input'); $myPost = array(); if (is_string($raw_post_data) && strlen($raw_post_data) > 0) { $raw_post_array = explode('&', $raw_post_data); foreach ($raw_post_array as $keyval) { $keyval = explode ('=', $keyval); if (count($keyval) == 2) { // Official PayPal sample: keep the timezone '+' in payment_date. if ($keyval[0] === 'payment_date' && substr_count($keyval[1], '+') === 1) { $keyval[1] = str_replace('+', '%2B', $keyval[1]); } $myPost[$keyval[0]] = urldecode($keyval[1]); } } } if (count($myPost) == 0 && is_array($_POST)) { $myPost = $_POST; } // read the IPN message sent from PayPal and prepend 'cmd=_notify-validate' $req = 'cmd=_notify-validate'; // get_magic_quotes_gpc() was removed in PHP 8, so this is always false there. $get_magic_quotes_exists = function_exists('get_magic_quotes_gpc'); foreach ($myPost as $key => $value) { if ($get_magic_quotes_exists && get_magic_quotes_gpc() == 1) { $value = urlencode(stripslashes($value)); } else { $value = urlencode($value); } $req .= "&$key=$value"; } $verify = ppVerifyIpnWithPaypal($req); echo("IPN verify attempts:\n" . implode("\n", $verify['attempts']) . "\n"); if ($verify['url']) echo("IPN " . $verify['status'] . " via " . $verify['url'] . "\n"); // Fail CLOSED: anything that is not an explicit "VERIFIED" must be treated // as unverified. A curl failure (network/TLS/timeout) previously fell // through both branches and the payment was credited as if PayPal had // confirmed it, which would credit an account for money never received. if ($verify['status'] === 'VERIFIED') { echo("VERIFIED\n"); } elseif ($verify['status'] === 'INVALID') { // IPN invalid, log for manual investigation $errorMessage = "ERROR: The PayPal server could not validate the IPN. The response from IPN was: INVALID"; echo("$errorMessage\n"); $unexpectedErrors = 1; } else { // curl failed, timed out, or returned an unexpected body. // Do not insert a blocking transaction row: PayPal must be able to retry. $errorMessage = "ERROR: Unable to verify IPN with PayPal. " . implode(" | ", $verify['attempts']); echo("$errorMessage\n"); error_log("ppPayment: $errorMessage"); http_response_code(503); exit(); } ################## End Post Back to PayPal ##################################### // Use the posted transaction number value to determine the school_num $TRXnum = $_POST['custom']; $query = "SELECT school_num FROM user_online_payments WHERE record_num = {$TRXnum} ORDER BY record_num DESC LIMIT 0,1"; $school_num = intval(pp_getVal($query)); if (!$school_num) { $business = $_POST['business']; $school_num = pp_getVal("SELECT record_num FROM schools WHERE paypal_code = '$business' LIMIT 0,1"); if (!$school_num) { $business = $_POST['receiver_email']; $school_num = pp_getVal("SELECT record_num FROM schools WHERE paypal_code = '$business' LIMIT 0,1"); if (!$school_num) $school_num = 0; } } $curr_school_num = $school_num; // Check if this entry is in the user_paypal_transactions table $payment_type = ''; $payment_status = ''; if (array_key_exists('payment_type', $_POST)) $payment_type = $_POST['payment_type']; if (array_key_exists('payment_status', $_POST)) $payment_status = $_POST['payment_status']; $txn_id = $_POST['txn_id']; $query = "SELECT * FROM user_paypal_transactions WHERE school_num = $school_num AND payment_type = '$payment_type' AND payment_status = '$payment_status' AND txn_id = '$txn_id' AND current_year != 'N'"; $r = pp_query($query); $trxID = 0; if (db_num_rows($r) > 0) { $existing = db_fetch_assoc($r); $existingResult = isset($existing['posting_result']) ? trim($existing['posting_result']) : ''; // Only skip IPNs that were already credited or fully handled. A previous // verify/network Error must remain retryable or the payment stays stuck. $alreadyHandled = in_array($existingResult, array('Posted', 'Reversed', 'Canceled Reversal', 'Pending'), true); if ($alreadyHandled) { $errorMessage = "Duplicate Posting - Transaction Has Already Been Processed: $txn_id"; echo("$errorMessage\n"); exit(); } $trxID = intval($existing['record_num']); echo("Reprocessing previous IPN attempt record_num=$trxID posting_result='$existingResult'\n"); } if (!$trxID) { // Create entry in user_paypal_transactions table $trxFields = array('transaction_subject','payment_date','txn_type','first_name','last_name','payer_email','payer_status','item_name', 'business','receiver_email','mc_currency','mc_gross','mc_fee','reference','payment_type','payment_status', 'pending_reason','txn_id','receipt_id','current_year'); $trxInsert = "school_num,posting_time,ptime,error_handled"; $trxValues = "$school_num,'$time',$ptime,'N'"; $current_year = 'Y'; for ($i = 0; $i < count($trxFields); $i++) { $fieldName = $trxFields[$i]; // Not every IPN carries every field (eg. reference, pending_reason, // receipt_id), so default to '' rather than emitting undefined-key // warnings and passing null into db_escape_string(). $fieldValue = ''; if (isset($$fieldName)) $fieldValue = $$fieldName; if (array_key_exists($fieldName, $_POST)) $fieldValue = db_escape_string(isset($_POST[$fieldName]) ? $_POST[$fieldName] : ''); $trxInsert .= ",$fieldName"; $trxValues .= ",'$fieldValue'"; } $trxSQL = "INSERT INTO user_paypal_transactions ($trxInsert) VALUES ($trxValues)"; // pp_query("START TRANSACTION"); pp_query($trxSQL); $trxID = db_insert_id(); echo("Insert ID: $trxID\n"); } extract($_POST,EXTR_PREFIX_ALL,'P'); $TRXnum = $P_custom; if (isset($P_case_type) && $P_case_type == 'complaint') { $errorMessage = "Non transaction Posting - Complaint Transaction: $txn_id"; echo("$errorMessage\n"); $query = "UPDATE user_paypal_transactions SET posting_result = 'Complaint', posting_message = '" . db_escape_string($errorMessage) . "' WHERE record_num = $trxID"; exit(); } if (isset($P_case_type) && $P_case_type == 'dispute') { $errorMessage = "Non transaction Posting - Dispute Transaction: $txn_id"; echo("$errorMessage\n"); $query = "UPDATE user_paypal_transactions SET posting_result = 'Dispute', posting_message = '" . db_escape_string($errorMessage) . "' WHERE record_num = $trxID"; exit(); } if (isset($P_case_type) && $P_case_type == 'chargeback') { $errorMessage = "Non transaction Posting - Chargeback Transaction: $txn_id"; echo("$errorMessage\n"); $query = "UPDATE user_paypal_transactions SET posting_result = 'Chargeback', posting_message = '" . db_escape_string($errorMessage) . "' WHERE record_num = $trxID"; exit(); } // If we were unable to verify the transaction if ($unexpectedErrors == 1) { $query = "SELECT * FROM user_online_payments WHERE record_num = {$TRXnum} AND status = 'Pending'"; $r = pp_query($query); if (db_num_rows($r) == 1) { extract(db_fetch_assoc($r), EXTR_PREFIX_ALL, 'p'); if (isset($p_user_num)) { pp_query("UPDATE user_paypal_transactions SET school_num = $p_school_num, user_num = $p_user_num, trx_num = $TRXnum WHERE record_num = $trxID"); } } // Update the log with the error message $query = "UPDATE user_paypal_transactions SET posting_result = 'Error', posting_message = '" . db_escape_string($errorMessage) . "' WHERE record_num = $trxID"; pp_query($query); exit(); } // Load user information from the database $query = "SELECT * FROM user_online_payments WHERE record_num = {$TRXnum}"; $r = pp_query($query); if (db_num_rows($r) != 1) { $errorMessage = "Invalid transaction number or status: $TRXnum"; echo("$errorMessage\n"); pp_query("UPDATE user_paypal_transactions SET posting_result = 'Error', posting_message = '" . db_escape_string($errorMessage) . "' WHERE record_num = $trxID"); } else { extract(db_fetch_assoc($r), EXTR_PREFIX_ALL, 'p'); } // Load school information from the database if (!isset($p_school_num) || intval($p_school_num) == 0) $p_school_num = $school_num; $r = pp_query("SELECT * FROM schools WHERE record_num = {$p_school_num}"); if (db_num_rows($r) != 1) { echo("Invalid school.\n"); pp_query("UPDATE user_paypal_transactions SET posting_result = 'Error', posting_message = 'Invalid school' WHERE record_num = $trxID"); exit(); } else { extract(db_fetch_assoc($r), EXTR_PREFIX_ALL, 's'); } // Update user_paypal_transactions with school and user info pp_query("UPDATE user_paypal_transactions SET school_num = {$p_school_num}, user_num = {$p_user_num}, trx_num = $TRXnum WHERE record_num = $trxID"); // Check for test/sandbox payments if (isset($P_test_ipn) && $P_test_ipn==1 && $s_paypal_mode == 'Live') { echo("Unexpected sandbox payment - we are not in testing mode.\n"); pp_query("UPDATE user_paypal_transactions SET posting_result = 'Error', posting_message = 'Unexpected sandbox payment - we are not in testing mode' WHERE record_num = $trxID"); exit(); } // Check that the payment was made in Canadian funds if (!$P_mc_currency || $P_mc_currency != $s_paypal_currency) { echo("Unexpected currency - payment should be made in $s_paypal_currency\n"); pp_query("UPDATE user_paypal_transactions SET posting_result = 'Error', posting_message = 'Unexpected currency' WHERE record_num = $trxID"); // Stop here: crediting a different currency at face value would post the // wrong amount to the user's account. error_log("ppPayment: currency mismatch $P_mc_currency => $s_paypal_currency (trx $TRXnum)"); exit(); } // // Process Reversed payments // if ($P_payment_status == 'Reversed') { pp_query("UPDATE user_paypal_transactions SET posting_result = 'Reversed', posting_message = 'Payment reversed' WHERE record_num = $trxID"); $results = pp_query("SELECT * FROM user_account WHERE payment_reference = '$P_parent_txn_id'"); $row = db_fetch_assoc($results); $order_payments = $row['order_payments']; $orders = explode("|",$order_payments); // Flag each order for ($i = 0; $i < count($orders); $i++) { if (array_key_exists($i, $orders)) { list($onum,$amt) = explode(":",preg_replace("/{(.*)}/","$1",$orders[$i])); if (intval($onum) > 0) { $query = "UPDATE hotlunch_order_header " . "SET status = 'Unpaid', " . "payment_method = '', " . "paid_date = NULL, " . "amount_paid = 0 " . "WHERE record_num = {$onum}"; pp_query($query); } // logOrderChange($onum,"PAYMENT REVERSED","ppPayment.php"); } } // Check if there is a user_account entry already $query = "SELECT * FROM user_account WHERE school_num = $p_school_num AND user_num = $p_user_num AND payment_reference = '$P_txn_id'"; $ua_entries = pp_query($query); if (db_num_rows($ua_entries) > 0) { echo("Duplicate: This transaction has been entered into user_account already: $P_txn_id\n"); exit(); } // Post transaction to the user_account date_default_timezone_set(mapTimeZone($s_timezone)); $entered_time = $ptime; $trx_time = date("Y-m-d H:i:s",$entered_time); $amount = $P_mc_gross * -1; $query = "INSERT INTO user_account " . "(school_num,user_num,schedule_num,transaction_datetime,transaction_type,note,transaction_amount,transaction_fee,payment_method,payment_reference,order_payments,deleted,entered_time,transaction_status) " . "VALUES ($p_school_num,$p_user_num,0,'$trx_time','Reversed Payment','PayPal Reversed Payment',$amount,$P_mc_fee,'PayPal','$P_txn_id','$order_payments','N',$entered_time,'Closed')"; pp_query($query); exit(); } // // Process Cancelled Reversed payments // if ($P_payment_status == 'Canceled_Reversal') { pp_query("UPDATE user_paypal_transactions SET posting_result = 'Canceled Reversal', posting_message = 'PayPal Canceled Reversal' WHERE record_num = $trxID"); $results = pp_query("SELECT * FROM user_account WHERE payment_reference = '$P_parent_txn_id'"); $row = db_fetch_assoc($results); $order_payments = $row['order_payments']; $orders = explode("|",$order_payments); // Flag each order for ($i = 0; $i < count($orders); $i++) { list($onum,$amt) = explode(":",preg_replace("/{(.*)}/","$1",$orders[$i])); if (intval($onum) > 0) { $query = "UPDATE hotlunch_order_header " . "SET status = 'Paid', " . "payment_method = 'PayPal', " . "amount_paid = order_total " . "WHERE record_num = {$onum}"; pp_query($query); } // logOrderChange($onum,"PAYMENT REVERSAL CANCELLED","ppPayment.php"); } // Check if there is a user_account entry already $query = "SELECT * FROM user_account WHERE school_num = $p_school_num AND user_num = $p_user_num AND payment_reference = '$P_txn_id'"; $ua_entries = pp_query($query); if (db_num_rows($ua_entries) > 0) { echo("Duplicate: This transaction has been entered into user_account already: $P_txn_id\n"); exit(); } // Post transaction to the user_account date_default_timezone_set(mapTimeZone($s_timezone)); $entered_time = $ptime; $trx_time = date("Y-m-d H:i:s",$entered_time); $amount = $P_mc_gross; $query = "INSERT INTO user_account " . "(school_num,user_num,schedule_num,transaction_datetime,transaction_type,note,transaction_amount,transaction_fee,payment_method,payment_reference,order_payments,deleted,entered_time,transaction_status) " . "VALUES ($p_school_num,$p_user_num,0,'$trx_time','Cancelled Reversed Payment','PayPal Cancelled Reversed Payment',$amount,$P_mc_fee,'PayPal','$P_txn_id','$order_payments','N',$entered_time,'Closed')"; pp_query($query); exit(); } // Check the payment was sent to the correct account if (strtolower(trim($P_receiver_email)) != strtolower(trim($s_paypal_code)) && strtolower(trim($P_business)) != strtolower(trim($s_paypal_code))) { echo("Payment was sent to an unexpected account - $s_paypal_code.\n"); echo("$P_receiver_email => $s_paypal_code\n"); pp_query("UPDATE user_paypal_transactions SET posting_result = 'Error', posting_message = 'Payment was sent to an unexpected account' WHERE record_num = $trxID"); // Stop here: the money did not land in this school's account, so crediting // the user would post funds that were never received. error_log("ppPayment: receiver mismatch $P_receiver_email => $s_paypal_code (trx $TRXnum)"); exit(); } // Update hotlunch_orders table $query = "SELECT hloh.* FROM hotlunch_order_header as hloh, people as p " . "WHERE hloh.school_num = {$p_school_num} " . "AND ordered_by_num = p.record_num " . "AND p.user_num = {$p_user_num} " . "AND hloh.status NOT IN ('Paid','Complimentary','Concession')"; $orders = pp_query($query); echo("Orders Found: " . db_num_rows($orders) . "\n\n"); // check for non-completed orders (ie. pending for some reason) if ($P_payment_status != 'Completed') { echo("Payment was not completed. $P_payment_status.\n"); if ($P_payment_status == 'Pending' && $P_payment_type == 'instant' && $P_pending_reason == 'paymentreview') { echo(" - Pending - instant - paymentreview\n"); } else { // Process refunds if ($P_payment_status == 'Refunded') { // Post transaction to the user_account date_default_timezone_set(mapTimeZone($s_timezone)); $entered_time = $ptime; $trx_time = date("Y-m-d H:i:s",$entered_time); // $snum = intval($snum); $amount = $P_mc_gross * -1; $query = "INSERT INTO user_account " . "(school_num,user_num,schedule_num,transaction_datetime,transaction_type,note,transaction_amount,transaction_fee,payment_method,payment_reference,order_payments,deleted,entered_time) " . "VALUES ($p_school_num,$p_user_num,0,'$trx_time','Refund','PayPal Refund',$amount,$P_mc_fee,'PayPal','$P_txn_id','','N',$entered_time)"; pp_query($query); pp_query("UPDATE user_paypal_transactions SET posting_result = 'Posted', posting_message = 'Refund posted' WHERE record_num = $trxID"); echo("Refund Status: $P_payment_status\n"); exit(); } // Process Pending transactions if ($P_payment_status == 'Pending') { pp_query("UPDATE user_paypal_transactions SET posting_result = 'Pending', posting_message = 'Payment not completed' WHERE record_num = $trxID"); // Flag each order if (db_num_rows($orders) > 0) { while ($row = db_fetch_array($orders)) { $onum = $row['record_num']; $query = "UPDATE hotlunch_order_header " . "SET status = 'Pending' " . "WHERE record_num = {$onum}"; pp_query($query); // logOrderChange($onum,"PENDING PAYMENT","ppPayment.php"); } } // Update users record to indicate that there are pending transactions $query = "update users set trx_status = 'Pending' where record_num = $p_user_num"; pp_query($query); echo("Pending Payment Status: $P_payment_status\n"); exit(); } // Process Failed transactions if ($P_payment_status == 'Failed') { pp_query("UPDATE user_paypal_transactions SET posting_result = 'Error', posting_message = 'Payment failed' WHERE record_num = $trxID"); // Find Pending Orders and reset the status $query = "SELECT hloh.* FROM hotlunch_order_header as hloh, people as p " . "WHERE hloh.school_num = {$p_school_num} " . "AND ordered_by_num = p.record_num " . "AND p.user_num = {$p_user_num} " . "AND hloh.status IN ('Pending')"; $orders = pp_query($query); echo("Orders Found: " . db_num_rows($orders) . "\n\n"); // Flag each order if (db_num_rows($orders) > 0) { while ($row = db_fetch_array($orders)) { $onum = $row['record_num']; $query = "UPDATE hotlunch_order_header " . "SET status = 'Unpaid' " . "WHERE record_num = {$onum}"; pp_query($query); // logOrderChange($onum,"PAYMENT FAILED","ppPayment.php"); } } // Update users record to clear the pending status $query = "update users set trx_status = '' where record_num = $p_user_num"; pp_query($query); echo("Failed Payment Status: $P_payment_status\n"); exit(); } $query = "UPDATE user_paypal_transactions SET posting_result = 'Error', posting_message = 'Payment not completed - $P_payment_status' WHERE record_num = $trxID"; pp_query($query); echo("Incomplete Payment Status: $P_payment_status\n"); exit(); } } echo("Payment validated ok - updating database\n\n"); // // Check if this transaction has been entered already // $query = "SELECT * FROM user_online_payments WHERE record_num = $TRXnum AND status = 'Paid'"; $paid_payments = pp_query($query); if (db_num_rows($paid_payments) > 0) { echo("Duplicate: This transaction has been posted to user_online_payments already: $TRXnum\n"); $row = db_fetch_assoc($paid_payments); if ($row['reference'] == $P_txn_id) exit(); echo("New PayPal Reference ID: $P_txn_id\n\n"); } // Update paypal_payments database table $P_mc_fee = floatval($P_mc_fee); $query = "UPDATE user_online_payments " . "SET status = 'Paid', payment_amount = balance_owing, reference = '{$P_txn_id}', trx_fee = {$P_mc_fee}, completed_time = $ptime " . "WHERE record_num = $TRXnum"; pp_query($query); $today = date("Y-m-d",$ptime); $order_payments = ""; $snum = 0; if (db_num_rows($orders) > 0) { while ($row = db_fetch_array($orders)) { $onum = $row['record_num']; $ototal = $row['order_total']; $opaid = $row['amount_paid']; $snum = $row['schedule_num']; $amt = number_format($ototal - $opaid,2); if (strlen($order_payments) > 0) $order_payments .= "|"; $order_payments .= "{" . $onum . ":" . $amt . "}"; $query = "UPDATE hotlunch_order_header " . "SET amount_paid = order_total, " . "status = 'Paid', " . "paid_date = '$today', " . "payment_method = 'PayPal' " . "WHERE record_num = {$onum}"; pp_query($query); // logOrderChange($onum,"PAYMENT COMPLETED","ppPayment.php"); } } // // Check if there is a user_account entry already // $query = "SELECT * FROM user_account WHERE school_num = $p_school_num AND user_num = $p_user_num AND payment_reference = '$P_txn_id'"; $ua_entries = pp_query($query); if (db_num_rows($ua_entries) > 0) { echo("Duplicate: This transaction has been entered into user_account already: $P_txn_id\n"); exit(); } // Post transaction to the user_account date_default_timezone_set(mapTimeZone($s_timezone)); $entered_time = $ptime; $trx_time = date("Y-m-d H:i:s",$entered_time); // Post transaction fee if there is one if ($p_transaction_fee != 0) { if ($p_transaction_fee > 0) { $query = "INSERT INTO user_account " . "(school_num,user_num,schedule_num,transaction_datetime,transaction_type,transaction_status,note,transaction_amount,amount_paid,transaction_fee,payment_method,payment_reference,order_payments,deleted,entered_time) " . "VALUES ($p_school_num,$p_user_num,$snum,'$trx_time','PayPal Transaction Fee','Closed','',$p_transaction_fee,$p_transaction_fee,0,'PayPal','$P_txn_id',NULL,'N',$entered_time)"; } else { $query = "INSERT INTO user_account " . "(school_num,user_num,schedule_num,transaction_datetime,transaction_type,transaction_status,note,transaction_amount,amount_paid,transaction_fee,payment_method,payment_reference,order_payments,deleted,entered_time) " . "VALUES ($p_school_num,$p_user_num,$snum,'$trx_time','PayPal Transaction Credit','Closed','',$p_transaction_fee,$p_transaction_fee,0,'PayPal','$P_txn_id',NULL,'N',$entered_time)"; } pp_query($query); $entered_time += 1; $trx_time = date("Y-m-d H:i:s",$entered_time); } // Insert payment transaction $amount = $P_mc_gross * -1; $query = "INSERT INTO user_account " . "(school_num,user_num,schedule_num,transaction_datetime,transaction_type,note,transaction_amount,transaction_fee,payment_method,payment_reference,order_payments,deleted,entered_time) " . "VALUES ($p_school_num,$p_user_num,$snum,'$trx_time','Payment','PayPal Payment',$amount,$P_mc_fee,'PayPal','$P_txn_id','$order_payments','N',$entered_time)"; pp_query($query); // Update any open charge transactions $query = "SELECT * FROM user_account " . "WHERE school_num = $school_num AND user_num = $p_user_num AND transaction_status = 'Open' AND transaction_amount > 0"; $results = pp_query($query); while ($row = db_fetch_assoc($results)) { $rnum = $row['record_num']; $query = "UPDATE user_account SET payment_method = 'PayPal', payment_reference = '$P_txn_id', payment_time = $entered_time " . "WHERE school_num = $school_num AND record_num = $rnum"; pp_query($query); } // Close all transactions $query = "SELECT record_num FROM user_account WHERE school_num = $school_num AND user_num = $p_user_num AND transaction_status != 'Closed'"; $results = pp_query($query); while ($row = db_fetch_assoc($results)) { $rnum = $row['record_num']; $query = "UPDATE user_account SET transaction_status = 'Closed' WHERE school_num = $school_num AND user_num = $p_user_num AND record_num = $rnum"; pp_query($query); } // Update all orders to set amount paid $query = "SELECT * FROM user_account WHERE school_num = $school_num AND user_num = $p_user_num AND transaction_amount > 0"; $results = pp_query($query); while ($row = db_fetch_assoc($results)) { $rnum = $row['record_num']; $query = "UPDATE user_account SET amount_paid = transaction_amount WHERE school_num = $school_num AND record_num = $rnum"; pp_query($query); } // Make activity_log entry $username = pp_getVal("SELECT userid FROM users WHERE record_num = $p_user_num"); $activity = "PayPal Payment"; $additional_info = "TRX ID: $P_txn_id\nAmount: " . number_format(abs($amount),2) . "\n"; $curr_time = $ptime; $activity_datetime = date("Y-m-d H:i:s",$curr_time); $uname = db_escape_string($username); $query = "insert into activity_log (school_num,userid,user_ip,activity_time,activity_datetime,activity,information) " . "values($p_school_num,'$uname','0.0.0.0',$curr_time,'$activity_datetime','$activity','$additional_info')"; pp_query($query); // Reset user transaction status $query = "update users set trx_status = NULL where record_num = $p_user_num"; pp_query($query); // Reset user can_order flag $query = "update users set can_order = 'Yes' where can_order = 'Balance' and record_num = $p_user_num"; pp_query($query); // Update user_paypal_transactions pp_query("UPDATE user_paypal_transactions SET posting_message = 'Payment processed ok' WHERE record_num = $trxID"); pp_query("UPDATE user_paypal_transactions SET posting_result = 'Posted' WHERE school_num = $p_school_num AND trx_num = $TRXnum"); // pp_query("COMMIT"); adjustUserAccountDetails($p_user_num,$p_school_num); echo("Payment processed ok\n\n\n"); ?>