<?php
/*
 * This file is part of CiviCRM.
 *
 * CiviCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CiviCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with CiviCRM.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2014-2017, Free Software Foundation
 * Copyright 2014-2017, Lisa Marie Maginnis <lisa@fsf.org>
 * Copyright 2015-2019, Ruben Rodriguez <ruben@fsf.org>
 *
 */

/**
  * CiviCRM (Instant Payment Notification) IPN processor module for
  * TrustCommerece.
  *
  * For full documentation on the
  * TrustCommerece API, please see the TCDevGuide for more information:
  * https://vault.trustcommerce.com/downloads/TCDevGuide.htm
  *
  * This module supports the following features: Single credit/debit card
  * transactions, AVS checking, recurring (create, update, and cancel
  * subscription) optional blacklist with fail2ban,
  *
  * @copyright Free Software Foundation 2014-2019
  * @version   2.0
  * @package   org.fsf.payment.trustcommerce.ipn
  */

define("MAX_FAILURES", 4);

class CRM_Core_Payment_trustcommerce_IPN extends CRM_Core_Payment_BaseIPN {

  /**
   * Inherit
   *
   * @return void
   */
  function __construct() {
    parent::__construct();
  }

  function getLastFailures($recur_id) {
    $sql="SELECT count(*) as numfails
          FROM civicrm_contribution
          WHERE contribution_recur_id = $recur_id
          AND
          id > (SELECT MAX(id) FROM civicrm_contribution WHERE contribution_recur_id = $recur_id
                AND contribution_status_id = 1);";
    $result = CRM_Core_DAO::executeQuery($sql);
    if($result->fetch()) {
      $failures = $result->numfails;
    } else {
      $failures = NULL;
    }
    return $failures;
  }

  function logmsg($msg){
      CRM_Core_Error::debug_log_message($msg);
      echo $msg."\n";
  }

  function main($component = 'contribute') {
    static $no = NULL;
    $billingid = CRM_Utils_Request::retrieve('billingid', 'String', $no, FALSE, 'GET');
    $input['status'] = CRM_Utils_Request::retrieve('status', 'String', $no, FALSE, 'GET');
    $input['amount'] = CRM_Utils_Request::retrieve('amount', 'String', $no, FALSE, 'GET');
    $input['date'] = CRM_Utils_Request::retrieve('date', 'String', $no, FALSE, 'GET');
    $input['trxn_id'] = CRM_Utils_Request::retrieve('trxn_id', 'String', $no, FALSE, 'GET');
    $checksum = CRM_Utils_Request::retrieve('checksum', 'String', $no, FALSE, 'GET');

    // Verify checksum
    if( $input['status'] == '' || $input['amount'] == '' || $input['date'] == '' || $input['trxn_id'] == '' || md5($billingid.$input['trxn_id'].$input['amount'].$input['date']) != $checksum) {
      $this->logmsg('ERROR: IPN called without proper fields');
      exit;
    }

    // get contribution_recur id and contact id
    $result = civicrm_api3('ContributionRecur', 'get', [
      'sequential' => 1,
      'processor_id' => $billingid,
    ]);
    if ($result['count'] == 0){
      $this->logmsg("ERROR: Could not find billingid: ".$billingid);
      exit();
    }
    $contribution_recur_id=$result['values'][0]['id'];
    $contact_id=$result['values'][0]['contact_id'];
    $contribution_recur_type_id=$result['values'][0]['contribution_type_id'];
    
    // If the recurring type is Donation (1) instead of Member Dues (2), check that it is correct!
    if ($contribution_recur_type_id == 1){
      $result = civicrm_api3('Membership', 'get', [
        'sequential' => 1,
        'contribution_recur_id' => $contribution_recur_id,
      ]);
      if ($result['count']!=0){
        $this->logmsg("WARNING: This recurring ID is associated with a membership, but listed as Donation. Correcting!");
        $result = civicrm_api3('ContributionRecur', 'setvalue', [
          'id' => $contribution_recur_id,
          'field' => "financial_type_id",
          'value' => 2,
        ]);
      }
    }

    // get first transaction on this recurring id
    $result = civicrm_api3('Contribution', 'get', [
      'sequential' => 1,
      'contribution_recur_id' => $contribution_recur_id,
    ]);
    if ($result['count'] == 0){
      $this->logmsg("WARNING: Could not find first contribution for billingid: ".$billingid);
    }else{
      $first_contribution_id=$result['values'][0]['contribution_id'];
      $first_status_id=$result['values'][0]['contribution_status_id'];

      // If first contribution is pending, and the IPN is Complete, complete original transaction
      if ($first_status_id == 2 && $input['status'] == 1){
        $result = civicrm_api3('Contribution', 'completetransaction', [
          'id' => $first_contribution_id,
          'trxn_id' => $input['trxn_id'],
          'trxn_date' => $input['date'],
          'payment_processor_id' => 7,
        ]);
        $this->logmsg('TrustCommerceIPN: Completed first contribution for contact: '.$contact_id.' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id'].' status: '.$input['status']);
        // TODO handle errors
      }
    }

    // check if IPN contribution already exists
    $existing_contribution = civicrm_api3('Contribution', 'get', [
      'sequential' => 1,
      'trxn_id' => $input['trxn_id'],
    ]);
    if ($existing_contribution['count'] != 0){
      $this->logmsg('TrustCommerceIPN: Skipping duplicate contribution for contact: '.$contact_id.' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id']);
      exit;
    }else{
      if ($input['status'] == 7){
      // TODO handle refunds
      }else{
        $send_receipt=1;
        if ($input['status'] == 4){
          $send_receipt=0;
          $status='Failed';
        }
        if ($input['status'] == 1){
          $status='Completed';
        }

        $result = civicrm_api3('Contribution', 'repeattransaction', [
          'original_contribution_id' => $first_contribution_id,
          'contribution_status_id' => $status,
          'contribution_recur_id' => $contribution_recur_id,
          'receive_date' => $input['date'],
          'trxn_id' => $input['trxn_id'],
          'is_email_receipt' => $send_receipt,
          'payment_processor_id' => 7,
          'total_amount' => $input['amount'],
        ]);
        $new_contribution_id=$result['id'];
        // TODO enable receipts for failed transactions
        // TODO handle errors
        // TODO un-hardcode payment_processor_id financial_type_id
      }
    }

    // Disable autorenew on failures
    if ($input['status'] == 4){
      $lastfailures = $this->getLastFailures($contribution_recur_id);
      if(MAX_FAILURES <= $lastfailures) {
        $this->disableAutorenew($billingid);
        // TODO handle errors
      }
    }

    $this->logmsg('TrustCommerceIPN: Created contribution for contact: '.$contact_id.' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id'].' status: '.$input['status']);
    return;
  }

  protected function disableAutorenew($recur_id) {
    $this->logmsg('TrustCommerceIPN: MAX_FAILURES hit, unstoring billing ID: '.$recur_id);

    $sql = "SELECT user_name, password, url_site FROM civicrm_payment_processor WHERE id =  8 LIMIT 1";
    $result = CRM_Core_DAO::executeQuery($sql);
    if($result->fetch()) {
      $request = array(
		      'custid' => $result->user_name,
		      'password' => $result->password,
		      'action' => 'unstore',
		      'billingid' => $recur_id
		      );

      // Mark recurring payment as Cancelled in civi
      $update = 'UPDATE civicrm_contribution_recur SET contribution_status_id = 3 WHERE processor_id = "'.$recur_id.'";';
      $result1 = CRM_Core_DAO::executeQuery($update);

      // Mark recurring payment as Cancelled in TrustCommerce
      $tc = tclink_send($request);
      if(!$tc) {
	return -1;
      }
      return TRUE;

    } else {
        $this->logmsg('CRITICAL ERROR: Could not load payment processor object');
      return;
    }
  }
}
