);
# Enable features, auto-includes
-require_once(DOCROOT . 'inc/wfpl/format.php');
-require_once(DOCROOT . 'inc/wfpl/db.php');
-require_once(DOCROOT . 'inc/wfpl/session_messages.php');
-require_once(DOCROOT . 'inc/session_auth.php');
-require_once(DOCROOT . 'inc/cms.php');
-require_once(DOCROOT . 'inc/misc.php');
+require_once(__DIR__.'/'.'inc/wfpl/format.php');
+require_once(__DIR__.'/'.'inc/wfpl/db.php');
+require_once(__DIR__.'/'.'inc/wfpl/session_messages.php');
+require_once(__DIR__.'/'.'inc/session_auth.php');
+require_once(__DIR__.'/'.'inc/cms.php');
+require_once(__DIR__.'/'.'inc/misc.php');
# Connect to the database
db_connect(WFPL_DB, WFPL_DB_USER, WFPL_DB_PASS);
db_upgrade();
}
+# paypal_ipn.php calls these when it receives a valid payment
+$GLOBALS['payment_handlers'] = [
+ # the key (below) must be the first word in the paypal variable "custom"
+ # the file will be run with wfpl's file_run()
+ # example:
+ #'membership' => DOCROOT . 'inc/payment_membership.php'
+];
+
$GLOBALS['email_templates'] = [
'backend_bug' => [
'title' => "Notification for site programmer(s)",
'description' => "This email template is used if/when the back-end code of this site encounters an unusual/suspicious situation that it's not sure how to cope with.",
'variables' => [
- ['message', "details about the unusual/suspicious situation"]
+ ['details', "details about the unusual/suspicious situation"]
],
'subject' => "backend alert",
- 'content' => "Hi developer,\n\nPlease investigate the following debugging message from the site:\n\n~message~"
+ 'content' => "Hi developer,\n\nPlease investigate the following debugging message from the site:\n\n~details~",
'from_addr' => 'noreply@example.com',
'to_addr' => 'fixme@example.com' # not all templates need this field
]
--- /dev/null
+<?php
+
+# this script does not use wfpl_main, so:
+require_once(__DIR__.'/'.'config.php');
+
+require_once(__DIR__.'/'.'inc/wfpl/template.php');
+require_once(__DIR__.'/'.'inc/wfpl/format.php');
+
+function paypal_ipn_main() {
+ // read the post from PayPal system and add 'cmd'
+ $req = 'cmd=_notify-validate';
+ $log = 'Received IPN:';
+
+ foreach ($_POST as $key => $value) {
+ $log .= "\n$key: $value";
+ $value = urlencode($value);
+ $req .= "&$key=$value";
+ }
+
+ // assign posted variables to local variables
+ $item_name = isset($_POST['item_name']) ? $_POST['item_name'] : '';
+ $item_number = isset($_POST['item_number']) ? $_POST['item_number'] : '';
+ $payment_status = isset($_POST['payment_status']) ? $_POST['payment_status'] : '';
+ $mc_gross = isset($_POST['mc_gross']) ? $_POST['mc_gross'] : '';
+ $mc_currency = isset($_POST['mc_currency']) ? $_POST['mc_currency'] : '';
+ $txn_id = isset($_POST['txn_id']) ? $_POST['txn_id'] : '';
+ $receiver_email = isset($_POST['receiver_email']) ? $_POST['receiver_email'] : '';
+ $payer_email = isset($_POST['payer_email']) ? $_POST['payer_email'] : '';
+ $custom = isset($_POST['custom']) ? $_POST['custom'] : '';
+ $txn_type = isset($_POST['txn_type']) ? $_POST['txn_type'] : '';
+ $subscr_id = isset($_POST['subscr_id']) ? $_POST['subscr_id'] : '';
+ $needs_review = 1;
+
+ $status = 'unknown';
+
+ $ch = curl_init($GLOBALS['paypal_site'] . '/cgi-bin/webscr');
+ if ($ch == false) {
+ $status = 'curl_init failed';
+ } else {
+ 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);
+ curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
+ $res = curl_exec($ch);
+ $curl_errno = curl_errno($ch);
+ curl_close($ch);
+ if ($curl_errno != 0) {
+ $status = 'curl fail: ' . $curl_errno;
+ } else {
+ // Split response headers and payload, a better way for strcmp
+ $tokens = explode("\r\n\r\n", trim($res));
+ $res = trim(end($tokens));
+ $res_word = trim($tokens[count($tokens) - 1]);
+ if ($res_word === 'VERIFIED') {
+ $status = 'verified';
+ } elseif ($res_word === 'INVALID') {
+ $status = 'invalid';
+ } else {
+ $log .= "\n\nCan't figure out PayPal verify reply:\n" . $res;
+ }
+ }
+ }
+
+ $row = [
+ 'txn_id' => $txn_id,
+ 'status' => $status,
+ 'custom' => $custom,
+ 'item_name' => $item_name,
+ 'item_number' => $item_number,
+ 'needs_review' => $needs_review,
+ 'payment_status' => $payment_status,
+ 'mc_gross' => $mc_gross,
+ 'mc_currency' => $mc_currency,
+ 'receiver_email' => $receiver_email,
+ 'payer_email' => $payer_email,
+ 'log' => $log,
+ 'txn_type' => $txn_type,
+ 'subscr_id' => $subscr_id,
+ 'user_id' => $user_id,
+ 'ipn_at' => time()
+ ];
+
+ db_insert_assoc('paypal_ipn', $row);
+ $row['id'] = $ipn_id = db_auto_id();
+
+ if($status !== 'verified') { # it's really from PayPal
+ paypal_ipn_main_debug("status is not \"verified\" but is \"$status\"");
+ } elseif ($txn_type !== 'subscr_payment' && $txn_type !== 'web_accept') {
+ if ($txn_type !== 'subscr_signup' && $txn_type !== 'subscr_cancel' && $txn_type !== 'subscr_eot') {
+ # subscr_cancel is sent when they cancel. After that:
+ # subscr_eot is sent when their next payment would have been
+ paypal_ipn_main_debug("txn_type is not \"subscr_payment\", \"subscr_signup\", \"subscr_cancel\", \"subscr_eot\" or \"web_accept\" but is \"$txn_type\"");
+ }
+ } elseif ($payment_status !== 'Completed') { # payment has completed
+ if ($payment_status !== 'Pending') {
+ paypal_ipn_main_debug("payment_status is not \"Completed\" or \"Pending\", but is \"$payment_status\"");
+ }
+ } elseif ($receiver_email !== $GLOBALS['paypal_email']) {
+ paypal_ipn_main_debug("payment isn't to us ($GLOBALS[paypal_email]) but to \"$receiver_email\"");
+ } elseif ($mc_currency !== 'USD') {
+ paypal_ipn_main_debug("Currency isn't \"USD\" but is \"$mc_currency\"");
+ } else {
+ $custom_words = explode(' ', $custom);
+ if (!isset($GLOBALS['payment_handlers'][$custom_words[0]])) {
+ paypal_ipn_main_debug("\$custom's first word isn't in GLOBALS[payment_handlers]. \$custom: \"$custom\"");
+ } else {
+ # FIXME add parameter with everything from paypal
+ $ret = file_run($GLOBALS['payment_handlers'][$custom_words[0]], $custom_words, $mc_gross, $row);
+ if ($ret and is_array($ret) and isset($ret['success']) and $ret['success']) {
+ $update = ['processed' => '1'];
+ if (isset($ret['for_table_id']) and isset($ret['for_row_id'])) {
+ $tid = format_int_0((string)$ret['for_table_id']);
+ $rid = format_int_0((string)$ret['for_row_id']);
+ if ((int)$tid > 0 and (int)$rid > 0) {
+ $update['for_table_id'] = $tid;
+ $update['for_row_id'] = $rid;
+ }
+ }
+ db_update_assoc('paypal_ipn', $update);
+ } else {
+ paypal_ipn_main_debug($user, $old_date, $was_expired);
+ }
+ }
+ }
+}
+
+function paypal_ipn_main_debug($message) {
+ $message = this_host() . ' paypal payment failure ' . $_POST['ipn_track_id'] . "\n\n" . $message;
+ $message .= "\n\nDump of all info received:\n";
+ foreach ($_POST as $key => $value) {
+ $message .= "\t$key: $value\n";
+ }
+ $template_vars = ['details' => $message];
+ email_with_template(null, 'backend_debug', $template_vars);
+}
+
+# this file is accessed directly from the paypal IPN system
+ini_set('display_errors', '0');
+ini_set('log_errors', '1');
+paypal_ipn_main();
--- /dev/null
+drop table if exists paypal_ipn;
+create table paypal_ipn (
+ id int unique auto_increment,
+ txn_id varchar(250) not null default "",
+ status varchar(250) not null default "",
+ ipn_at int(11) not null default 0
+ txn_type varchar(100) not null default "",
+ subscr_id varchar(100) not null default "",
+ custom varchar(250) not null default "",
+ for_table_id int not null default 0,
+ for_row_id int not null default 0,
+ processed int(1) not null default 0,
+ item_name varchar(250) not null default "",
+ item_number varchar(250) not null default "",
+ needs_review int(1) not null default 0,
+ payment_status varchar(250) not null default "",
+ mc_gross varchar(250) not null default "",
+ mc_currency varchar(250) not null default "",
+ receiver_email varchar(250) not null default "",
+ payer_email varchar(250) not null default "",
+ log text not null default "");