Files
2026-10-20 17:31:06 -07:00

1039 lines
42 KiB
PHP

<?php
$noSession = 1;
require("db.php");
include("include/school-functions.php");
include("include/logging-functions.php");
include("include/date-time.php");
include "table.php";
if (!is_array($_POST) || count($_POST) == 0) {
// $contents = file_get_contents("/m/data1/hotlunches.net/ppPayment.log");
// echo(nl2br(htmlentities($contents)));
// echo($contents);
echo("Permission denied");
exit();
}
$ptime = time();
$time = date("Y-m-d H:i:s",$ptime);
$ltime = date("Y-m-d.H.i.s",$ptime);
// start capturing output
//
// NOTE: logging here must never be fatal. This runs inside the ob_start()
// handler at script shutdown, so an error escaping this function kills the
// response and PayPal sees a 500 and retries the IPN - even though the
// payment itself was already processed successfully.
function ppWriteLog($path, $contents, $mode = "w") {
$dir = dirname($path);
if (!is_dir($dir)) @mkdir($dir, 0775, true);
$f = @fopen($path, $mode);
if ($f === false) {
error_log("ppPayment: unable to open log file $path");
return;
}
@fwrite($f, $contents);
@fclose($f);
}
function saveOutput($contents) {
global $ltime, $school_num;
// Log beside this script unless PPPAYMENT_LOGDIR overrides it, so the
// path follows the deployment instead of being pinned to one server.
$BASEDIR = getenv('PPPAYMENT_LOGDIR');
if (!$BASEDIR) $BASEDIR = __DIR__;
ppWriteLog("$BASEDIR/ppPayment.log", $contents, "w");
ppWriteLog("$BASEDIR/ppPayment.trx.log", $contents, "a");
ppWriteLog("$BASEDIR/logs/paypal/pp.$school_num.$ltime", $contents, "w");
return nl2br(htmlentities($contents));
}
// function to execute a mysql query after echoing the query statement and return result
function pp_query($query) {
echo("{$query}\n");
$r = db_query($query);
if (stripos($query,"update") !== false || stripos($query,"insert") !== false || stripos($query,"delete") !== false) {
$n = db_affected_rows();
echo("Affected Rows: $n\n");
}
return($r);
}
// function to execute a mysql query after echoing the query statement and return value
function pp_getVal($query) {
echo("{$query}\n");
$r = db_getVal($query);
return($r);
}
function ppCacertPath() {
$cacert = dirname(__FILE__) . '/cacert.pem';
return is_file($cacert) ? $cacert : '';
}
function ppAddResolvedIp(&$ips, $ip) {
$ip = trim($ip, "[] \t\r\n");
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
if (!in_array($ip, $ips['v4'], true)) $ips['v4'][] = $ip;
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
if (!in_array($ip, $ips['v6'], true)) $ips['v6'][] = $ip;
}
}
function ppParseDohAnswers($json) {
$ips = array('v4' => 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");
?>