<?php

require_once 'classes/PluginCallback.php';
require_once 'modules/billing/models/class.gateway.plugin.php';

class PluginPaypalCallback extends PluginCallback
{
    
    function processCallback()
    {
        
        if (!isset($GLOBALS['testing'])) {
            $testing = false;
        } else {
            $testing = $GLOBALS['testing'];
        }
        
        $this->logger->log(5, 'Paypal callback invoked');

        if ($this->settings->get('plugin_paypal_Use PayPal Sandbox') == '0'
            && isset($_POST['test_ipn']) && $_POST['test_ipn'] == '1')
        {
            $this->logger->log(5, "** Paypal sandbox callback but account is in production mode => callback discarded");
            return;
        }

        if (!isset($_POST['txn_type'])) {
            $this->logger->log(5, 'Paypal callback ignored: txn_type is not defined');
            return;
        }
        
        ////////////////////////////////////
        // read post from PayPal system and add 'cmd'
        $req = 'cmd=_notify-validate';
        
        foreach ($_POST as $key => $value) {
          $value = urlencode(stripslashes($value));
          $req .= "&$key=$value";
        }
        
        // post back to PayPal system to validate
        if (isset($_POST['test_ipn']) && $_POST['test_ipn'] == 1) {
            $paypal_url = 'www.sandbox.paypal.com';
        } else {
            $paypal_url = 'www.paypal.com';
        }
        $header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= 'Content-Length: ' . strlen($req) . "\r\n\r\n";
        
        // Assign IPN Variables
        // Different possible transaction types (txn_type) for subscriptions:
        // 1. 'subscr_signup': issued just after the client has paid the inicial amount. We ignore this.
        // 2. 'subscr_payment': issued just after the previous one and for every subsequent payment.
        $ppTransType = $_POST['txn_type'];

        if ($ppTransType == 'subscr_signup') {
            $this->logger->log(5, 'Paypal subscr_signup callback ignored');
            return;
        }

        $ppTransID = @$_POST['txn_id']; // Transaction ID (Unique) (not defined for subscription cancellations)
        $ppPayStatus = @$_POST['payment_status']; // Payment Status (not defined for subscription cancellations)
        $ppPayAmount = @$_POST['mc_gross']; // Total paid for this transaction (not defined for subscription cancellations)
        list($tInvoiceID,$tIsRecurring,$tGenerateInvoice) = explode("_", $_POST["custom"]);
        $this->logger->log(5, "\$ppTransType: $ppTransType; \$ppTransID: $ppTransID; \$ppPayStatus: $ppPayStatus;");
        $this->logger->log(5, "\$ppPayAmount: $ppPayAmount; \$tInvoiceID: $tInvoiceID; \$tIsRecurring: $tIsRecurring; \$tGenerateInvoice: $tGenerateInvoice");
        
        // Create Plugin class object to interact with CE.
        $cPlugin = new Plugin($tInvoiceID, 'paypal', $this->user, $this->customer);

        $newInvoice = false;
        
        // Check to see if this Invoice is already paid.
        // Skip if this is a call for a cancellation
        if($cPlugin->IsPaid()==1 && $ppTransType == 'subscr_payment') {
            $this->logger->log(5, 'Previous subscription invoice is already paid');
            // If it is, then check to see if the GenerateInvoice variable was passed to the script
            if($tGenerateInvoice && $tIsRecurring) {
                $this->logger->log(5, 'Generating new invoice');
                // If it was and is TRUE (1), Generate a New Invoice, and set internal Plugin object
                // variables to the new invoice's information.
                $newInvoice = $cPlugin->GenerateNewInvoice($ppPayAmount,'', $ppTransID);
            } else {
                $this->logger->log(1, 'Error: exiting Paypal callback invocation');
                exit;
            }
        }
        
        $this->logger->log(5, "Requesting callback confirmation; sending request: $paypal_url?$req");
        $res = $this->_requestConfirmation($paypal_url, $header.$req, $testing);
        
        
        //Add plugin details
        $cPlugin->m_Amount = $ppPayAmount;
        $cPlugin->m_TransactionID = $ppTransID;
        $cPlugin->m_Action = "charge";
        $cPlugin->m_Last4 = "NA";
        
        if (strcmp ($res, "VERIFIED") == 0) {
            $this->logger->log(5, "Callback has been verified successfully");
            // TODO check that receiver_email is an email address in your PayPal account
            if($tIsRecurring) {
                // Uncomment to test payment failures through subscription cancellations
                // if ($ppTransType == 'subscr_cancel') $ppTransType = 'subscr_failed';
                switch($ppTransType) {
                    case 'subscr_payment':
                        switch($ppPayStatus) {
                            case "Completed":
                                // Payment was succesful/completed.
                                $transaction = "Paypal payment of $ppPayAmount was accepted. Original Signup Invoice: $tInvoiceID (OrderID:".$ppTransID.")";
                                $cPlugin->PaymentAccepted($ppPayAmount,$transaction,$ppTransID, $testing);
                                if ($newInvoice) {
                                    $cPlugin->updateDateRecurringEntry();
                                }
                            break;
                            case "Pending":
                                $transaction = "Paypal payment of $ppPayAmount was marked 'pending' by Paypal. Original Signup Invoice: $tInvoiceID (OrderID:".$ppTransID.")";
                                $cPlugin->PaymentAccepted($ppPayAmount,$transaction,$ppTransID, $testing);
                            break;
                            case "Failed":
                                $transaction = "Paypal payment of $ppPayAmount was rejected. Original Signup Invoice: $tInvoiceID (OrderID:".$ppTransID.")";
                                $cPlugin->PaymentRejected($transaction);
                            break;
                        }
                        break;
                    case 'subscr_cancel':
                        $this->logger->log(5, "Subscription has been cancelled on Paypal's side.");
                        $subject = 'Gateway recurring payment cancelled';
                        $message = "Recurring payment for invoice $tInvoiceID, corresponding to package \"{$_POST['item_name']}\" has been cancelled by customer.";
                        // If createTicket returns false it's because this transaction has already been done
                        // Use subscr_id because txn_id is not sent on cancellation IPNs from Paypal
                        if (!$cPlugin->createTicket($_POST['subscr_id'], $subject, $message)) exit;
        
                        // Reset recurringfee entry so that invoice entry gets generated without the IPN call.
                        // We don't use the nextbilldate in the recurringfee table cause there was a bug in this plugin's
                        // early implementation that wasn't updating that field. So we calculate it from scratch every time.
                        // First we need to determine the next due date.
                        $cycles = array(
                            'D' => 'day',
                            'M' => 'month',
                            'Y' => 'year',
                        );
                        // Warning: don't use $_POST['subscr_date'] here: when $_POST['txn_type'] is 'subscr_cancel' this contains
                        // the cancellation date, not the subscription date.
                        
                        /* There are four possible cases that a subscription cancellation can occur.
                         *   1) The subscription is cancelled before the next due date
                         *   2) The subscription is cancelled on the due date (before the payment has gone through)
                         *   3) The subscription is cancelled on the due data (after the payment has gone through)
                         *   4) The subscription is cancelled after two failed payment attempts (payment overdue)
                         *
                         * Cases 1 & 4 are similiar and cases 2 & 3 are similiar.  We need to distinguish between each set of cases.
                         */
                        $nextDueDate = $cPlugin->m_Invoice->getDate('timestamp');
                        if (isset($_POST['period1'])) {
                            $period = explode(' ', $_POST['period1']);
                            $nextDueDate = strtotime("+{$period[0]} {$cycles[$period[1]]}", $nextDueDate);
                        }
                        $today = mktime(0, 0, 0, date("m") , date("d"), date("Y"));
                        if ($nextDueDate < $today) {
                            $period = explode(' ', $_POST['period3']);
                            while ($nextDueDate < $today) {
                                $nextDueDate = strtotime("+{$period[0]} {$cycles[$period[1]]}", $nextDueDate);
                            }
                            // If nextduedate is today then we must check to see if the payment has already gone through
                            if ($nextDueDate == $today) {
                                // Cases 2 & 3: if case 2 then do nothing.  if case 3 we must push the date forward one cycle.
                                // Check for case 3 by seeing if there is a subscription payment for the proper amount already paid today.
                                $query = "SELECT customerid, amount FROM invoice WHERE id=?";
                                $result = $this->db->query($query, $tInvoiceID);
                                list ($customerId, $amount) = $result->fetch();
                                // If there is already an invoice marked paid today for that customer and the same invoice amount
                                // then update the nextduedate one cycle in the future.
                                $query = "SELECT id FROM invoice WHERE amount<=? AND amount>=? AND UNIX_TIMESTAMP(billdate)=? AND id != ? AND customerid = ?";
                                $result = $this->db->query($query, $amount + 0.0001, $amount - 0.0001, $today, $tInvoiceID, $customerId);
                                if ($result->getNumRows() == 1) {
                                    // Case 3
                                    $nextDueDate = strtotime("+{$period[0]} {$cycles[$period[1]]}", $nextDueDate);
                                }
                            } else {
                                // Cases 1 & 4:   if case 1 then do nothing.  if case 4 we must roll back the date one cycle.
                                // Check for case 4 by seeing if there is a paid invoice one cycle in the past.
                                $lastDueDate = strtotime("-{$period[0]} {$cycles[$period[1]]}", $nextDueDate);
                                $query = "SELECT customerid, amount FROM invoice WHERE id=?";
                                $result = $this->db->query($query, $tInvoiceID);
                                list ($customerId, $amount) = $result->fetch();
                                // If there is not already an invoice marked paid on the last due date for that customer and the same invoice amount
                                // then update the nextduedate one cycle in the past.
                                $query = "SELECT id FROM invoice WHERE amount<=? AND amount>=? AND UNIX_TIMESTAMP(billdate)=? AND customerid = ?";
                                $result = $this->db->query($query, $amount + 0.0001, $amount - 0.0001, $lastDueDate, $customerId);
                                if ($result->getNumRows() == 0) {
                                    // Case 4
                                    $nextDueDate = $lastDueDate;
                                }
                            }
                        }
                        $transaction = "Paypal subscription cancelled by customer. Original Signup Invoice: $tInvoiceID";
                        $cPlugin->resetRecurring($transaction, $_POST['subscr_id'], $nextDueDate);
                        break;
                    case 'subscr_failed':
                        // this is caused by lack of funds for example
                        $subject = 'Gateway recurring payment failed';
                        $reason = isset($_POST['pending_reason'])? $_POST['pending_reason'] : 'unknown';
                        $message = "Recurring payment for invoice $tInvoiceID, corresponding to package \"{$_POST['item_name']}\" has failed.\n";
                        $message .= "Reason: $reason.";
                        // if createTicket returns false it's because this transaction has already been done
                        if (!$cPlugin->createTicket($_POST['subscr_id'], $subject, $message)) exit;
                        // log failed transaction
                        $transaction = "Recurring subscription payment failed. Reason: $reason.";
                        $cPlugin->logFailedSubscriptionPayment($transaction, $_POST['subscr_id']);
                        break;
                }
            } else {
                // Add Code for Normal Payment
                switch($ppPayStatus) {
                    case "Completed":
                        // Payment was succesful/completed.
                        $transaction = "Paypal payment of $ppPayAmount was accepted. Original Signup Invoice: $tInvoiceID (OrderID:".$ppTransID.")";
                        $cPlugin->PaymentAccepted($ppPayAmount,$transaction,$ppTransID, $testing);
                    break;
                    case "Pending":
                        $transaction = "Paypal payment of $ppPayAmount was accepted. Original Signup Invoice: $tInvoiceID (OrderID:".$ppTransID.")";
                        $cPlugin->PaymentAccepted($ppPayAmount,$transaction,$ppTransID, $testing);
                    break;
                    case "Failed":
                        $transaction = "Paypal payment of $ppPayAmount was rejected. Original Signup Invoice: $tInvoiceID (OrderID:".$ppTransID.")";
                        $cPlugin->PaymentRejected($transaction);
                    break;
                }
            }
        } elseif (strcmp ($res, "INVALID") == 0) {
            $this->logger->log(5, "Callback verification returned 'INVALID'");
            $transaction = "Paypal IPN returned INVALID to Confirmation.";
            $cPlugin->PaymentRejected($transaction);
        }
    }
    
    function _requestConfirmation($paypal_url, $request, $testing)
    {
        if ($testing) {
            return 'VERIFIED';
        } else {
            // no credit card info transmitted, so no need to use SSL
            $fp = fsockopen ($paypal_url, 80, $errno, $errstr, 30);
            if (!$fp) {
                echo "$errstr ($errno)\n";
                exit;
            }
            fputs($fp, $request);
            $res = '';
            while (!feof($fp)) {
                $res = fgets($fp, 1024);
            }
            fclose($fp);
            return $res;
        }
    }
}

?>
