From e4c10cb295066ba8f65983e0be05b7c7768d5ea8 Mon Sep 17 00:00:00 2001 From: Ruben Rodriguez Date: Mon, 11 Mar 2019 15:36:04 -0400 Subject: [PATCH] Re-implemented with api3 calls --- trustcommerceIPN.php | 367 +++++++++++++------------------------------ 1 file changed, 113 insertions(+), 254 deletions(-) diff --git a/trustcommerceIPN.php b/trustcommerceIPN.php index 3f459bc..8cb0389 100644 --- a/trustcommerceIPN.php +++ b/trustcommerceIPN.php @@ -17,6 +17,7 @@ * * Copyright 2014-2017, Free Software Foundation * Copyright 2014-2017, Lisa Marie Maginnis + * Copyright 2015-2019, Ruben Rodriguez * */ @@ -32,8 +33,8 @@ * transactions, AVS checking, recurring (create, update, and cancel * subscription) optional blacklist with fail2ban, * - * @copyright Free Software Foundation 2014-2017 - * @version 1.0 + * @copyright Free Software Foundation 2014-2019 + * @version 2.0 * @package org.fsf.payment.trustcommerce.ipn */ @@ -57,20 +58,22 @@ class CRM_Core_Payment_trustcommerce_IPN extends CRM_Core_Payment_BaseIPN { 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; + 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'); @@ -78,77 +81,121 @@ class CRM_Core_Payment_trustcommerce_IPN extends CRM_Core_Payment_BaseIPN { $input['trxn_id'] = CRM_Utils_Request::retrieve('trxn_id', 'String', $no, FALSE, 'GET'); $checksum = CRM_Utils_Request::retrieve('checksum', 'String', $no, FALSE, 'GET'); - if ($billingid) { - if( $input['status'] == '' || $input['amount'] == '' || $input['date'] == '' || $input['trxn_id'] == '' || md5($billingid.$input['trxn_id'].$input['amount'].$input['date']) != $checksum) { - CRM_Core_Error::debug_log_message("Error: IPN called with out proper fields"); - echo "Error: invalid paramaters

\n"; - exit; - } - - - $ids = $objects = array(); - $input['component'] = $component; - - // load post ids in $ids - $ids = NULL; - $ids = $this->getIDs($billingid, $input, $input['component']); - - $ids['trxn_id'] = $input['trxn_id']; + // 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; + } - if($this->checkDuplicate($input, $ids) != NULL) { - $msg = 'TrustCommerceIPN: Skipping duplicate contribution: '.$ids['contribution'].' for contact: '.$ids['contact'].' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id']."\n"; - echo $msg; - CRM_Core_Error::debug_log_message($msg); - 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_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_type_id == 1){ + $result = civicrm_api3('Membership', 'get', [ + 'sequential' => 1, + 'contribution_recur_id' => $contribution_recur_id, + ]); + // Found a membership for that recurring id, correcting + $this->logmsg("WARNING: This recurring ID is associated with a membership, but listed as Donation. Correcting!"); + if ($result['count']!=0){ + $result = civicrm_api3('ContributionRecur', 'setvalue', [ + 'id' => $contribution_recur_id, + 'field' => "financial_type_id", + 'value' => 2, + ]); + } + } - if(array_key_exists('membership', $ids)) { - $membership = array(); - $params = array('id' => $ids['membership']); - $obj = CRM_Member_BAO_Membership::retrieve($params, $membership); - $objects['membership'] = array(&$obj); - } + // 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']; + } - $paymentProcessorTypeID = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType', - 'TrustCommerce', 'id', 'name' - ); - $paymentProcessorID = civicrm_api3('PaymentProcessor', 'getvalue', array( - 'is_test' => 0, - 'options' => array('limit' => 1), - 'payment_processor_type_id' => $paymentProcessorTypeID, - 'return' => '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 + } - if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) { - return FALSE; - } - // var_dump($objects); - - if ($component == 'contribute' && $ids['contributionRecur']) { - // check if first contribution is completed, else complete first contribution - $first = TRUE; - if ($objects['contribution']->contribution_status_id == 1) { - $first = FALSE; + // 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'; } - - return $this->processRecur($input, $ids, $objects, $first); - + $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, + 'financial_type_id' => 2, + 'payment_processor_id' => 7, + ]); + $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) { - /* Load payment processor object */ - // HARD CODED - $msg = 'TrustCommerceIPN: MAX_FAILURES hit, unstoring billing ID: '.$recur_id."\n"; - - CRM_Core_Error::debug_log_message($msg); - echo $msg; + $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( @@ -158,208 +205,20 @@ class CRM_Core_Payment_trustcommerce_IPN extends CRM_Core_Payment_BaseIPN { '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); + $result1 = CRM_Core_DAO::executeQuery($update); + // Mark recurring payment as Cancelled in TrustCommerce $tc = tclink_send($request); if(!$tc) { return -1; } - return TRUE; } else { - echo 'CRITICAL ERROR: Could not load payment processor object'; + $this->logmsg('CRITICAL ERROR: Could not load payment processor object'); return; } - - } - - protected function checkDuplicate($input, $ids) { - // $sql='select id from civicrm_contribution where receive_date like \''.$input['date'].'%\' and total_amount='.$input['amount'].' and contact_id='.$ids['contact'].' and contribution_status_id = 1 limit 1'; - $sql="select id from civicrm_contribution where trxn_id = '".$ids['trxn_id']."' and contribution_status_id != 2"; - - $result = CRM_Core_DAO::executeQuery($sql); - if($result->fetch()) { - $id = $result->id; - } else { - $id = NULL; - } - - return $id; } - protected function processRecur($input, $ids, $objects, $first) { - $lastfailures = $this->getLastFailures($ids['contributionRecur']); - $recur = &$objects['contributionRecur']; - $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - - $transaction = new CRM_Core_Transaction(); - - $now = date('YmdHis'); - - // fix dates that already exist - $dates = array('create_date', 'start_date', 'end_date', 'cancel_date', 'modified_date'); - foreach ($dates as $name) { - if ($recur->$name) { - $recur->$name = CRM_Utils_Date::isoToMysql($recur->$name); - } - } - - if (!$first) { - // create a contribution and then get it processed - $contribution = new CRM_Contribute_BAO_Contribution(); - $contribution->contact_id = $ids['contact']; - $contribution->financial_type_id = $objects['contributionType']->id; - $contribution->contribution_page_id = $ids['contributionPage']; - $contribution->contribution_recur_id = $ids['contributionRecur']; - $contribution->receive_date = $input['date']; - $contribution->currency = $objects['contribution']->currency; - $contribution->payment_instrument_id = 1; - $contribution->amount_level = $objects['contribution']->amount_level; - $contribution->address_id = $objects['contribution']->address_id; - $contribution->campaign_id = $objects['contribution']->campaign_id; - $contribution->total_amount = $input['amount']; - - $objects['contribution'] = &$contribution; - } - - $objects['contribution']->invoice_id = md5(uniqid(rand(), TRUE)); - // $objects['contribution']->total_amount = $objects['contribution']->total_amount; - $objects['contribution']->trxn_id = $input['trxn_id']; - - // check if contribution is already completed, if so we ignore this ipn - if ($objects['contribution']->contribution_status_id == 1) { - $transaction->commit(); - CRM_Core_Error::debug_log_message("returning since contribution has already been handled"); - echo 'Success: Contribution has already been handled

'; - echo ''; - return TRUE; - } - - $sendNotification = FALSE; - - $recur->trxn_id = $input['trxn_id']; - $recur->total_amount = $input['amount']; - $recur->payment_instrument_id = 1; - $recur->fee = 0; - $recur->net_amount = $input['amount']; - - $objects['contribution']->save(); - - if ($input['status'] == 1) { - - // Approved - if ($first) { - $recur->start_date = $now; - $sendNotification = TRUE; - $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_START; - } - $statusName = 'In Progress'; - if (($recur->installments > 0) && - ($input['subscription_paynum'] >= $recur->installments) - ) { - // this is the last payment - $statusName = 'Completed'; - $recur->end_date = $now; - - $sendNotification = TRUE; - $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_END; - } - - $recur->modified_date = $now; - $recur->contribution_status_id = array_search($statusName, $contributionStatus); - $recur->save(); - $input['is_test'] = 0; - $msg = 'TrustCommerceIPN: Created contribution: '.$ids['contribution'].' for contact: '.$ids['contact'].' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id'].' status: Completed'."\n"; - echo $msg; - CRM_Core_Error::debug_log_message($msg); - - $this->completeTransaction($input, $ids, $objects, $transaction, $recur); - } - else if( $input['status'] == 4 ) { - // Declined - // failed status - - $recur->contribution_status_id = array_search('Failed', $contributionStatus); - $recur->cancel_date = $now; - $recur->save(); - - $msg = 'TrustCommerceIPN: Created contribution: '.$ids['contribution'].' for contact: '.$ids['contact'].' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id'].' status: Failed'."\n"; - echo $msg; - CRM_Core_Error::debug_log_message($msg); - - /* Disable cancelling transactions */ - $input['IAmAHorribleNastyBeyondExcusableHackInTheCRMEventFORMTaskClassThatNeedsToBERemoved'] = 1; - - /* Action for repeated failures */ - if(MAX_FAILURES <= $lastfailures) { - //$this->disableAutoRenew(($ids['contributionRecur'])); - $this->disableAutorenew($ids['processor_id']); - } - - return $this->failed($objects, $transaction, $input); - } - - if ($sendNotification) { - $autoRenewMembership = FALSE; - if ($recur->id && isset($ids['membership']) && $ids['membership'] ) { - $autoRenewMembership = TRUE; - } - - //send recurring Notification email for user - CRM_Contribute_BAO_ContributionPage::recurringNotify($subscriptionPaymentStatus, - $ids['contact'], - $ids['contributionPage'], - $recur, - $autoRenewMembership - ); - } - } - - protected function getIDs($billingid, $input, $module) { - $sql = "SELECT cr.id, cr.contact_id, co.id as coid - FROM civicrm_contribution_recur cr - INNER JOIN civicrm_contribution co ON co.contribution_recur_id = cr.id - WHERE cr.processor_id = '$billingid' LIMIT 1"; - - $result = CRM_Core_DAO::executeQuery($sql); - $result->fetch(); - $ids['contribution'] = $result->coid; - $ids['contributionRecur'] = $result->id; - $ids['contact'] = $result->contact_id; - $ids['processor_id'] = $billingid; - - if (!$ids['contributionRecur']) { - CRM_Core_Error::debug_log_message("Could not find billingid: ".$billingid); - echo "Failure: Could not find contributionRecur: $billingid

\n"; - exit(); - } - - // get page id based on contribution id - $ids['contributionPage'] = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', - $ids['contribution'], - 'contribution_page_id' - ); - - if ($module == 'event') { - // FIXME: figure out fields for event - } - else { - // get the optional ids - - // Get membershipId. Join with membership payment table for additional checks - $sql = "SELECT m.id - FROM civicrm_membership as m - WHERE m.contribution_recur_id = '{$ids['contributionRecur']}' - LIMIT 1"; - if ($membershipId = CRM_Core_DAO::singleValueQuery($sql)) { - - $ids['membership'] = $membershipId; - } - - } - - return $ids; - } - } -- 2.25.1