From d6ed7dae6a8eca2b3648d85226f28ebeed88f1f2 Mon Sep 17 00:00:00 2001 From: ethem Date: Mon, 12 Dec 2005 17:32:00 +0000 Subject: [PATCH] CREDIT & VOID implementation. TO DO: - admin.php for course payment management. - Transaction cut-off time --- enrol/authorize/action.php | 166 ++++++++++++++++++++++--------- enrol/authorize/db/mysql.php | 24 +++++ enrol/authorize/db/postgres7.php | 25 ++++- enrol/authorize/enrol.php | 83 ++++++++-------- enrol/authorize/version.php | 2 +- 5 files changed, 209 insertions(+), 91 deletions(-) diff --git a/enrol/authorize/action.php b/enrol/authorize/action.php index 129d02fedfa..7c5555a3c55 100644 --- a/enrol/authorize/action.php +++ b/enrol/authorize/action.php @@ -3,37 +3,35 @@ /** * No action. */ -define('AN_ACTION_NONE', 0x0); - +define('AN_ACTION_NONE', 0x00); /** * Authorize only. Don't capture. */ -define('AN_ACTION_AUTH_ONLY', 0x1); - +define('AN_ACTION_AUTH_ONLY', 0x01); /** * Authorized before, capture now. */ -define('AN_ACTION_PRIOR_AUTH_CAPTURE', 0x2); +define('AN_ACTION_PRIOR_AUTH_CAPTURE', 0x02); /** * Authorize and capture */ -define('AN_ACTION_AUTH_CAPTURE', 0x3); -/** - * Cancel transaction in status - * - authorized - * - auth_captured/pending settle - * - * @todo cut-off time - */ -define('AN_ACTION_VOID', 0x4); +define('AN_ACTION_AUTH_CAPTURE', 0x03); /** * Refund it. - * must be in 120 days after settled. - * auth_captured/settled. + * must be performed in 120 days after settled. + * status must be auth_captured/settled. + * can be credited up to original amount. + * -- generates new transid. SAVE IT. */ -define('AN_ACTION_CREDIT', 0x8); - - +define('AN_ACTION_CREDIT', 0x04); +/** + * Cancel transaction in status + * - authorized: don't settle. + * - auth_captured/pending settle + * - credited mistakenly, void it. + * @todo cut-off time + */ +define('AN_ACTION_VOID', 0x08); /** * authorizenet_action @@ -45,15 +43,15 @@ define('AN_ACTION_CREDIT', 0x8); * @return bool true, transaction was successful, false otherwise * @author Ethem Evlice * @uses $CFG - * @todo AN_ACTION_VOID and AN_ACTION_CREDIT + * @todo cut-off time */ function authorizenet_action(&$order, &$message, $action=AN_ACTION_NONE, $extra = null) { global $CFG; static $conststring; - + $an_test = !empty($CFG->an_test); - + if (empty($conststring)) { $consdata = array ( 'x_version' => '3.1', @@ -68,14 +66,14 @@ function authorizenet_action(&$order, &$message, $action=AN_ACTION_NONE, $extra $str = ''; foreach($consdata as $ky => $vl) { $str .= $ky . '=' . urlencode($vl) . '&'; } $str .= (!empty($CFG->an_tran_key)) ? - "x_tran_key" . "=" . urlencode($CFG->an_tran_key): + "x_tran_key" . "=" . urlencode($CFG->an_tran_key): "x_password" . "=" . urlencode($CFG->an_password); - $conststring = $str; + $conststring = $str; } - + $action = intval($action); - + // sanity check if (empty($order) || empty($order->id)) { $message = "check order->id!"; @@ -85,7 +83,7 @@ function authorizenet_action(&$order, &$message, $action=AN_ACTION_NONE, $extra $message = "no action taken!"; return false; } - + $poststring = $conststring; switch ($action) { case AN_ACTION_AUTH_ONLY: @@ -97,7 +95,7 @@ function authorizenet_action(&$order, &$message, $action=AN_ACTION_NONE, $extra } if (empty($extra)) { $message = "need extra fields!"; - return false; + return false; } $ext = (array)$extra; $poststring .= "&" . "x_type=" . ($action==AN_ACTION_AUTH_ONLY ? "AUTH_ONLY" : "AUTH_CAPTURE"); @@ -113,16 +111,79 @@ function authorizenet_action(&$order, &$message, $action=AN_ACTION_NONE, $extra $message = "order->status must be AN_STATUS_AUTH!"; return false; } + // 30 days. +1 = cut-off time + $timediff = time() - (31 * 3600 * 24); + if ($order->timecreated < $timediff) { + $message = "Transaction must be captured within 30 days. EXPIRED!"; + return false; + } $poststring .= "&" . "x_type=PRIOR_AUTH_CAPTURE"; $poststring .= "&" . "x_trans_id=" . urlencode($order->transid); - break; + break; } - - case AN_ACTION_VOID: // 30 days - case AN_ACTION_CREDIT: // 120 days + + case AN_ACTION_CREDIT: { - $message = "not implemented yet!"; - return false; + if ($order->status != (AN_STATUS_AUTH | AN_STATUS_CAPTURE)) { + $message = "order->status must be AN_STATUS_AUTH & AN_STATUS_CAPTURE!"; + return false; + } + // 120 days + $timediff = time() - (120 * 3600 * 24); + if ($order->timecreated < $timediff) { + $message = "Order can be credited within 120 days!"; + return false; + } + // up to original amount + $total = doubleval($extra->sum) + doubleval($extra->amount); + if ($total > $order->amount) { + $message = "Can be credited up to original amount."; + return false; + } + $poststring .= "&" . "x_type=CREDIT"; + $poststring .= "&" . "x_trans_id=" . urlencode($order->transid); + $poststring .= "&" . "x_card_num=" . urlencode(sprintf("%04d", intval($order->cclastfour))); + $poststring .= "&" . "x_currency_code=" . urlencode($extra->currency); + $poststring .= "&" . "x_amount=" . urlencode($extra->amount); + break; + } + + case AN_ACTION_VOID: + { + // only: authonly, authcapture, credit + if ($order->status != AN_STATUS_AUTH && + $order->status != (AN_STATUS_AUTH | AN_STATUS_CAPTURE) && + $order->status != AN_STATUS_CREDIT) { + $message = "order->status must be AUTH, AUTH_CAPTURE or CREDIT!"; + return false; + } + + if ($order->status == AN_STATUS_AUTH) { + // 30 days for authonly, make it expired (***********timeupdated) + $timediff = time() - (30 * 3600 * 24); + if ($order->timecreated < $timediff) { + $message = "Auth_only transaction can be voided within 30 days!"; + $order->status = AN_STATUS_EXPIRED; + return false; + } + } elseif ($order->status == (AN_STATUS_AUTH | AN_STATUS_CAPTURE)) { + // 1 day. Cancel pending settlement. + $timediff = time() - (2 * 3600 * 24); // TO DO: Cut-off time + if ($order->timecreated < $timediff) { + $message = "Settled transaction cannot be voided. Try REFUND!"; + return false; + } + } elseif ($order->status == AN_STATUS_CREDIT) { + // 120 days for credit + $timediff = time() - (120 * 3600 * 24); + if ($order->timecreated < $timediff) { + $message = "Ops! Settled transaction must be credited within 120 days!"; + return false; + } + } + // OK. + $poststring .= "&" . "x_type=VOID"; + $poststring .= "&" . "x_trans_id=" . urlencode($order->transid); } default: { // ??? @@ -130,13 +191,13 @@ function authorizenet_action(&$order, &$message, $action=AN_ACTION_NONE, $extra return false; } } - + // referer $anrefererheader = ''; if (!(empty($CFG->an_referer) || $CFG->an_referer == "http://" || $CFG->an_referer == "https://")) { $anrefererheader = "Referer: " . $CFG->an_referer . "\r\n"; } - + $response = array(); $connect_host = $an_test ? AN_HOST_TEST : AN_HOST; $fp = fsockopen("ssl://" . $connect_host, AN_PORT, $errno, $errstr, 60); @@ -144,7 +205,7 @@ function authorizenet_action(&$order, &$message, $action=AN_ACTION_NONE, $extra $message = "no connection: $errstr ($errno)"; return false; } - + fputs($fp, "POST " . AN_PATH . " HTTP/1.0\r\n" . "Host: $connect_host\r\n" . $anrefererheader . @@ -153,7 +214,7 @@ function authorizenet_action(&$order, &$message, $action=AN_ACTION_NONE, $extra "Content-length: " . strlen($poststring) . "\r\n\r\n" . $poststring . "\r\n" ); - + $tmpstr = ''; while(!feof($fp) && !stristr($tmpstr, 'content-length')) { $tmpstr = fgets($fp, 4096); @@ -179,26 +240,35 @@ function authorizenet_action(&$order, &$message, $action=AN_ACTION_NONE, $extra if (substr($response[$rcount], -1) == AN_ENCAP) { $response[$rcount] = substr($response[$rcount], 0, -1); } - + if ($response[0] == AN_APPROVED) { - $order->authcode = strval($response[4]); // Authorization or Approval code - $order->avscode = strval($response[5]); // Address Verification System code - $order->transid = strval($response[6]); // TransactionID + $order->transid = strval($response[6]); // TransactionID. $order->timeupdated = time(); switch ($action) { case AN_ACTION_AUTH_ONLY: - $order->status |= AN_STATUS_AUTH; - break; - case AN_ACTION_AUTH_CAPTURE: - $order->status = AN_STATUS_AUTH | AN_STATUS_CAPTURE; + $order->authcode = strval($response[4]); // Authorization or Approval code + $order->avscode = strval($response[5]); // Address Verification System code + if ($action == AN_ACTION_AUTH_ONLY) { + $order->status = AN_STATUS_AUTH; + } + else { + $order->status = AN_STATUS_AUTH | AN_STATUS_CAPTURE; + } break; case AN_ACTION_PRIOR_AUTH_CAPTURE: - $order->status |= AN_STATUS_CAPTURE; + $order->status = AN_STATUS_AUTH | AN_STATUS_CAPTURE; + break; + + case AN_ACTION_CREDIT: // generates new TransactionID + $order->status = AN_STATUS_CREDIT; + break; + + case AN_ACTION_VOID: + $order->status = AN_STATUS_VOID; break; } - return true; } else { diff --git a/enrol/authorize/db/mysql.php b/enrol/authorize/db/mysql.php index 463758af06f..6686bd690ce 100755 --- a/enrol/authorize/db/mysql.php +++ b/enrol/authorize/db/mysql.php @@ -36,6 +36,30 @@ function authorize_upgrade($oldversion=0) { execute_sql(" UPDATE {$CFG->prefix}enrol_authorize SET timecreated='$timenow', timeupdated='$timenow', status='$status' ", false); } + if ($oldversion < 2005121200) { + // new fields for refund and sales reports. + $defaultcurrency = empty($CFG->enrol_currency) ? 'USD' : $CFG->enrol_currency; + table_column('enrol_authorize', '', 'amount', 'varchar', '10', '', '0', 'not null', 'timeupdated'); + table_column('enrol_authorize', '', 'currency', 'char', '3', '', $defaultcurrency, 'not null', 'amount'); + modify_database("","CREATE TABLE prefix_enrol_authorize_refunds ( + `id` int(10) unsigned NOT NULL auto_increment, + `orderid` int(10) unsigned NOT NULL default 0, + `refundtype` int(1) unsigned NOT NULL default 0, + `amount` varchar(10) NOT NULL default '', + `transid` int(10) unsigned NULL default 0, + PRIMARY KEY (`id`), + KEY `orderid` (`orderid`) + );"); + // defaults. + if ($courses = get_records_select('course', '', '', 'id, cost, currency')) { + foreach ($courses as $course) { + execute_sql("UPDATE {$CFG->prefix}enrol_authorize + SET amount = '$course->cost', currency = '$course->currency' + WHERE courseid = '$course->id'", false); + } + } + } + return $result; } diff --git a/enrol/authorize/db/postgres7.php b/enrol/authorize/db/postgres7.php index dce914019a5..452e544a9d6 100644 --- a/enrol/authorize/db/postgres7.php +++ b/enrol/authorize/db/postgres7.php @@ -20,7 +20,7 @@ function authorize_upgrade($oldversion=0) { modify_database('',"CREATE INDEX prefix_enrol_authorize_courseid_idx ON prefix_enrol_authorize (courseid);"); modify_database('',"CREATE INDEX prefix_enrol_authorize_userid_idx ON prefix_enrol_authorize (userid);"); } - + if ($oldversion < 2005112100) { include_once("$CFG->dirroot/enrol/authorize/enrol.php"); @@ -35,6 +35,29 @@ function authorize_upgrade($oldversion=0) { $status = AN_STATUS_AUTH | AN_STATUS_CAPTURE; execute_sql(" UPDATE {$CFG->prefix}enrol_authorize SET timecreated='$timenow', timeupdated='$timenow', status='$status' ", false); } + + if ($oldversion < 2005121200) { + // new fields for refund and sales reports. + $defaultcurrency = empty($CFG->enrol_currency) ? 'USD' : $CFG->enrol_currency; + table_column('enrol_authorize', '', 'amount', 'varchar', '10', '', '0', 'not null', 'timeupdated'); + table_column('enrol_authorize', '', 'currency', 'char', '3', '', $defaultcurrency, 'not null', 'amount'); + modify_database("","CREATE TABLE prefix_enrol_authorize_refunds ( + id SERIAL PRIMARY KEY, + orderid INTEGER NOT NULL default 0, + refundtype INTEGER NOT NULL default 0, + amount varchar(10) NOT NULL default '', + transid INTEGER NULL default 0 + );"); + modify_database("","CREATE INDEX prefix_enrol_authorize_refunds_orderid_idx ON prefix_enrol_authorize_refunds (orderid);"); + // defaults. + if ($courses = get_records_select('course', '', '', 'id, cost, currency')) { + foreach ($courses as $course) { + execute_sql("UPDATE {$CFG->prefix}enrol_authorize + SET amount = '$course->cost', currency = '$course->currency' + WHERE courseid = '$course->id'", false); + } + } + } return $result; } diff --git a/enrol/authorize/enrol.php b/enrol/authorize/enrol.php index 96a6e8ee85f..7ab4df68bb7 100755 --- a/enrol/authorize/enrol.php +++ b/enrol/authorize/enrol.php @@ -15,29 +15,29 @@ define('AN_ENCAP', '"'); * New order. No transaction was made. */ define('AN_STATUS_NONE', 0x00); - /** * Authorized. */ define('AN_STATUS_AUTH', 0x01); - /** * Captured. */ define('AN_STATUS_CAPTURE', 0x02); - +/** + * Refunded. + */ +define('AN_STATUS_CREDIT', 0x04); +/** + * Voided. + */ +define('AN_STATUS_VOID', 0x08); /** * Expired. */ -define('AN_STATUS_EXPIRED', 0x10); - - - - +define('AN_STATUS_EXPIRE', 0x10); require_once("$CFG->dirroot/enrol/enrol.class.php"); - /** * enrolment_plugin_authorize * @@ -51,7 +51,7 @@ class enrolment_plugin extends enrolment_base */ var $ccerrormsg; - + /** * Shows a credit card form for registration. * @@ -128,7 +128,7 @@ class enrolment_plugin extends enrolment_base } } - + /** * Credit card number mode. * Send to authorize.net. @@ -149,9 +149,9 @@ class enrolment_plugin extends enrolment_base $this->ccerrormsg = get_string("allfieldsrequired"); return; } - + $this->prevent_double_paid($course); - + $exp_date = ($form->ccexpiremm < 10) ? strval('0'.$form->ccexpiremm) : strval($form->ccexpiremm); $exp_date .= $form->ccexpireyyyy; $valid_cc = CCVal($form->cc, $form->cctype, $exp_date); @@ -162,7 +162,7 @@ class enrolment_plugin extends enrolment_base $this->ccerrormsg = get_string( (($valid_cc===0) ? 'ccexpired' : 'ccinvalid'), 'enrol_authorize' ); return; } - + // NEW ORDER $timenow = time(); $order = new stdClass(); @@ -182,7 +182,7 @@ class enrolment_plugin extends enrolment_base $this->ccerrormsg = "Insert record error. Admin has been notified!"; return; } - + $extra = new stdClass(); $extra->x_first_name = $form->ccfirstname; $extra->x_last_name = $form->cclastname; @@ -204,10 +204,10 @@ class enrolment_plugin extends enrolment_base $extra->x_fax = ''; $extra->x_invoice_num = $order->id; $extra->x_description = $course->shortname; - + $message = null; $an_review = !empty($CFG->an_review); - $action = $an_review ? AN_ACTION_AUTH_ONLY : AN_ACTION_AUTH_CAPTURE; + $action = $an_review ? AN_ACTION_AUTH_ONLY : AN_ACTION_AUTH_CAPTURE; $success = authorizenet_action($order, $message, $action, $extra); if ($success) { @@ -221,11 +221,11 @@ class enrolment_plugin extends enrolment_base $this->email_to_admin("Error while trying to update data. Please edit manually this record: " . "ID=$order->id in enrol_authorize table.", $order); } - redirect($CFG->wwwroot, get_string("emailnotify", "enrol_authorize"), '60'); + redirect($CFG->wwwroot, get_string("reviewnotify", "enrol_authorize"), '60'); return; } - - // credit card captured, ENROL... + + // credit card captured, ENROL... if (!update_record("enrol_authorize", $order)) { $this->email_to_admin( "Error while trying to update data. Please edit manually this record: " . "ID=$order->id in enrol_authorize table.", $order); @@ -285,7 +285,7 @@ class enrolment_plugin extends enrolment_base } } - + /** * zero_cost * @@ -297,8 +297,8 @@ class enrolment_plugin extends enrolment_base $curcost = $this->get_course_cost($course); return (abs($curcost['cost']) < 0.01); } - - + + /** * get_course_cost * @@ -309,7 +309,7 @@ class enrolment_plugin extends enrolment_base function get_course_cost($course) { global $CFG; - + $cost = (float)0; $currency = (!empty($course->currency)) ? $course->currency :( empty($CFG->enrol_currency) @@ -318,7 +318,7 @@ class enrolment_plugin extends enrolment_base if (!empty($course->cost)) { $cost = (float)(((float)$course->cost) < 0) ? $CFG->enrol_cost : $course->cost; } - + $cost = format_float($cost, 2); $ret = array('cost' => $cost, 'currency' => $currency); @@ -358,7 +358,7 @@ class enrolment_plugin extends enrolment_base return $str; } - + /** * Shows config form & errors * @@ -384,7 +384,7 @@ class enrolment_plugin extends enrolment_base if (empty($CFG->loginhttps)) { notify("\$CFG->loginhttps must be ON"); } - + // ******************* AUTOCAPTURE ******************* if (!(empty($frm->an_review) || $frm->an_review_day < 1)) { // ++ENABLED++ @@ -412,7 +412,7 @@ class enrolment_plugin extends enrolment_base include($CFG->dirroot.'/enrol/authorize/config.html'); } - + /** * process_config * @@ -423,7 +423,7 @@ class enrolment_plugin extends enrolment_base function process_config($config) { global $CFG; - + // ENROL config set_config('enrol_cost', optional_param('enrol_cost', 5, PARAM_INT) ); set_config('enrol_currency', optional_param('enrol_currency', 'USD', PARAM_ALPHA) ); @@ -432,7 +432,7 @@ class enrolment_plugin extends enrolment_base set_config('enrol_mailadmins', optional_param('enrol_mailadmins', '') ); // AUTHORIZE.NET config - + // not required! set_config('an_test', optional_param('an_test', '') ); set_config('an_referer', optional_param('an_referer', 'http://', PARAM_URL) ); @@ -473,11 +473,11 @@ class enrolment_plugin extends enrolment_base set_config('an_review', $review_val); set_config('an_review_day', $review_day_val); } - + return true; } - + /** * email_to_admin * @@ -496,8 +496,8 @@ class enrolment_plugin extends enrolment_base } email_to_user($admin, $admin, "CC ERROR: ".$subject, $message); } - - + + /** * prevent_double_paid * @@ -513,15 +513,15 @@ class enrolment_plugin extends enrolment_base redirect($CFG->wwwroot . '/login/logout.php'); return; } - + if ($rec = get_record('enrol_authorize', 'userid',$USER->id, 'courseid',$course->id, 'status',AN_STATUS_AUTH, 'id')) { $a->orderid = $rec->id; - redirect($CFG->wwwroot, get_string("paymentproggress", "enrol_authorize", $a), '20'); + redirect($CFG->wwwroot, get_string("paymentpending", "enrol_authorize", $a), '20'); return; } } - + /** * check_openssl_loaded * @@ -532,7 +532,7 @@ class enrolment_plugin extends enrolment_base return extension_loaded('openssl'); } - + /** * cron * @access public @@ -556,21 +556,22 @@ class enrolment_plugin extends enrolment_base delete_records_select('enrol_authorize', $select); } } - + if ($random100 > 80) { // EXPIRED: Transactions with auth_only will be expired 30 days later. $select = "(status = '" .AN_STATUS_AUTH. "') AND (timeupdated = '0') AND (timecreated < '$timediff30')"; - execute_sql("UPDATE {$CFG->prefix}enrol_authorize SET timeupdated = '$timenow', status = '" .AN_STATUS_EXPIRED. "' WHERE $select", false); + execute_sql("UPDATE {$CFG->prefix}enrol_authorize SET timeupdated = '$timenow', status = '" .AN_STATUS_EXPIRE. "' WHERE $select", false); } if (empty($CFG->an_review) || empty($CFG->an_review_day) || $CFG->an_review_day < 1) { // AUTOCAPTURE disabled. admin, teacher review it manually - return; + return; } // AUTO-CAPTURE: it must be captured within 30 days. Otherwise it will expired. $timediffcnf = $timenow - (intval($CFG->an_review_day) * 3600 * 24); $select = "(status = '" . AN_STATUS_AUTH . "') AND (timeupdated = '0') AND (timecreated < '$timediffcnf') AND (timecreated > '$timediff30')"; if ($orders = get_records_select('enrol_authorize', $select)) { + set_time_limit(0); require_once("$CFG->dirroot/enrol/authorize/action.php"); $this->log = "AUTHORIZE.NET AUTOCAPTURE CRON: " . userdate($timenow) . "\n"; $message = null; diff --git a/enrol/authorize/version.php b/enrol/authorize/version.php index ff8010a6b2f..a2416103884 100755 --- a/enrol/authorize/version.php +++ b/enrol/authorize/version.php @@ -1,6 +1,6 @@ version = 2005112100; +$module->version = 2005121200; $module->requires = 2005072200; ?>