Integration as the * "API Callback URL' * Order processing commands can be sent automatically by placing these * commands appropriately * * To use this code for merchant-calculated feedback, this url must be * set also as the merchant-calculations-url when the cart is posted * Depending on your calculations for shipping, taxes, coupons and gift * certificates update parts of the code as required * */ require_once 'CRM/Core/Payment/BaseIPN.php'; define('GOOGLE_DEBUG_PP', 1); /** * Class org_civicrm_payment_googlecheckout_GoogleIPN */ class org_civicrm_payment_googlecheckout_GoogleIPN extends CRM_Core_Payment_BaseIPN { /** * We only need one instance of this object. So we use the singleton * pattern and cache the instance in this variable * * @var object */ static private $_singleton = NULL; /** * mode of operation: live or test * * @var object */ static protected $_mode = NULL; /** * @param $name * @param $type * @param $object * @param bool $abort * * @return mixed */ static function retrieve($name, $type, $object, $abort = TRUE) { $value = CRM_Utils_Array::value($name, $object); if ($abort && $value === NULL) { CRM_Core_Error::debug_log_message("Could not find an entry for $name"); echo "Failure: Missing Parameter
"; exit(); } if ($value) { if (!CRM_Utils_Type::validate($value, $type)) { CRM_Core_Error::debug_log_message("Could not find a valid entry for $name"); echo "Failure: Invalid Parameter
";
exit();
}
}
return $value;
}
/**
* Constructor
*
* @param string $mode the mode of operation: live or test
*
* @param $paymentProcessor
*
* @return \org_civicrm_payment_googlecheckout_GoogleIPN
*/
function __construct($mode, &$paymentProcessor) {
parent::__construct();
$this->_mode = $mode;
$this->_paymentProcessor = $paymentProcessor;
}
/**
* The function gets called when a new order takes place.
*
* @param xml $dataRoot response send by google in xml format
* @param array $privateData contains the name value pair of ";
return;
}
// lets replace invoice-id with google-order-number because thats what is common and unique
// in subsequent calls or notifications sent by google.
$contribution->invoice_id = $input['newInvoice'];
$input['amount'] = $dataRoot['order-total']['VALUE'];
if ($contribution->total_amount != $input['amount']) {
CRM_Core_Error::debug_log_message("Amount values dont match between database and IPN request");
echo "Failure: Amount values dont match between database and IPN request ";
return;
}
if (!$this->getInput($input, $ids)) {
return FALSE;
}
require_once 'CRM/Core/Transaction.php';
$transaction = new CRM_Core_Transaction();
// check if contribution is already completed, if so we ignore this ipn
if ($contribution->contribution_status_id == 1) {
CRM_Core_Error::debug_log_message("returning since contribution has already been handled");
echo "Success: Contribution has already been handled ";
}
else {
/* Since trxn_id hasn't got any use here,
* lets make use of it by passing the eventID/membershipTypeID to next level.
* And change trxn_id to google-order-number before finishing db update */
if ($ids['event']) {
$contribution->trxn_id = $ids['event'] . CRM_Core_DAO::VALUE_SEPARATOR . $ids['participant'];
}
else {
$contribution->trxn_id = $ids['membership'] . CRM_Core_DAO::VALUE_SEPARATOR . $ids['related_contact'] . CRM_Core_DAO::VALUE_SEPARATOR . $ids['onbehalf_dupe_alert'];
}
}
$contribution->save();
$transaction->commit();
return TRUE;
}
/**
* The function gets called when the state(CHARGED, CANCELLED..) changes for an order
*
* @param string $status status of the transaction send by google
* @param $dataRoot
* @param $component
* @internal param array $privateData contains the name value pair of ";
exit();
}
// Google sends the charged notification twice.
// So to make sure, code is not executed again.
if ($contribution->contribution_status_id == 1) {
CRM_Core_Error::debug_log_message("Contribution already handled (ContributionID = $contribution).");
exit();
}
$objects['contribution'] = &$contribution;
$ids['contribution'] = $contribution->id;
$ids['contact'] = $contribution->contact_id;
$ids['event'] = $ids['participant'] = $ids['membership'] = NULL;
$ids['contributionRecur'] = $ids['contributionPage'] = NULL;
if ($input['component'] == "event") {
list($ids['event'], $ids['participant']) = explode(CRM_Core_DAO::VALUE_SEPARATOR,
$contribution->trxn_id
);
}
else {
list($ids['membership'], $ids['related_contact'], $ids['onbehalf_dupe_alert']) = explode(CRM_Core_DAO::VALUE_SEPARATOR,
$contribution->trxn_id
);
foreach (array('membership', 'related_contact', 'onbehalf_dupe_alert') as $fld) {
if (!is_numeric($ids[$fld])) {
unset($ids[$fld]);
}
}
}
$this->loadObjects($input, $ids, $objects);
require_once 'CRM/Core/Transaction.php';
$transaction = new CRM_Core_Transaction();
if ($status == 'PAYMENT_DECLINED' ||
$status == 'CANCELLED_BY_GOOGLE' ||
$status == 'CANCELLED'
) {
return $this->failed($objects, $transaction);
}
$input['amount'] = $contribution->total_amount;
$input['fee_amount'] = NULL;
$input['net_amount'] = NULL;
$input['trxn_id'] = $orderNo;
$input['is_test'] = $contribution->is_test;
$this->completeTransaction($input, $ids, $objects, $transaction);
}
/**
* singleton function used to manage this object
*
* @param string $mode the mode of operation: live or test
*
* @param $component
* @param $paymentProcessor
* @return object
*/
static function &singleton($mode, $component, &$paymentProcessor) {
if (self::$_singleton === NULL) {
self::$_singleton = new org_civicrm_payment_googlecheckout_GoogleIPN($mode, $paymentProcessor);
}
return self::$_singleton;
}
/**
* The function retrieves the amount the contribution is for, based on the order-no google sends
*
* @param int $orderNo ";
exit();
}
return $contribution->total_amount;
}
/**
* The function returns the component(Event/Contribute..), given the google-order-no and merchant-private-data
*
* @param xml $xml_response response send by google in xml format
* @param array $privateData contains the name value pair of ";
exit();
}
if (stristr($contribution->source, ts('Online Contribution'))) {
$module = 'Contribute';
}
elseif (stristr($contribution->source, ts('Online Event Registration'))) {
$module = 'Event';
}
$isTest = $contribution->is_test;
}
else {
$contribution = new CRM_Contribute_DAO_Contribution();
$contribution->invoice_id = $orderNo;
if (!$contribution->find(TRUE)) {
CRM_Core_Error::debug_log_message("Could not find contribution record with invoice id: $orderNo");
echo "Failure: Could not find contribution record with invoice id: $orderNo ";
exit();
}
if (stristr($contribution->source, ts('Online Contribution'))) {
$module = 'Contribute';
}
elseif (stristr($contribution->source, ts('Online Event Registration'))) {
$module = 'Event';
}
$isTest = $contribution->is_test;
}
if ($contribution->contribution_status_id == 1) {
//contribution already handled.
exit();
}
if ($module == 'Contribute') {
if (!$contribution->contribution_page_id) {
CRM_Core_Error::debug_log_message("Could not find contribution page for contribution record: $contributionID");
echo "Failure: Could not find contribution page for contribution record: $contributionID ";
exit();
}
// get the payment processor id from contribution page
$paymentProcessorID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage',
$contribution->contribution_page_id,
'payment_processor_id'
);
}
else {
if ($root == 'new-order-notification') {
$eventID = $privateData['eventID'];
}
else {
list($eventID, $participantID) = explode(CRM_Core_DAO::VALUE_SEPARATOR,
$contribution->trxn_id
);
}
if (!$eventID) {
CRM_Core_Error::debug_log_message("Could not find event ID");
echo "Failure: Could not find eventID ";
exit();
}
// we are in event mode
// make sure event exists and is valid
require_once 'CRM/Event/DAO/Event.php';
$event = new CRM_Event_DAO_Event();
$event->id = $eventID;
if (!$event->find(TRUE)) {
CRM_Core_Error::debug_log_message("Could not find event: $eventID");
echo "Failure: Could not find event: $eventID ";
exit();
}
// get the payment processor id from contribution page
$paymentProcessorID = $event->payment_processor_id;
}
if (!$paymentProcessorID) {
CRM_Core_Error::debug_log_message("Could not find payment processor for contribution record: $contributionID");
echo "Failure: Could not find payment processor for contribution record: $contributionID ";
exit();
}
return array($isTest, $module, $paymentProcessorID);
}
/**
* This method is handles the response that will be invoked (from extern/googleNotify) every time
* a notification or request is sent by the Google Server.
* @param $xml_response
*/
static
function main($xml_response) {
require_once ('Google/library/googleresponse.php');
require_once ('Google/library/googlemerchantcalculations.php');
require_once ('Google/library/googleresult.php');
require_once ('Google/library/xml-processing/xmlparser.php');
$config = CRM_Core_Config::singleton();
// Retrieve the XML sent in the HTTP POST request to the ResponseHandler
if (get_magic_quotes_gpc()) {
$xml_response = stripslashes($xml_response);
}
require_once 'CRM/Utils/System.php';
$headers = CRM_Utils_System::getAllHeaders();
if (GOOGLE_DEBUG_PP) {
CRM_Core_Error::debug_var('RESPONSE', $xml_response, TRUE, TRUE, 'Google');
}
// Retrieve the root and data from the xml response
$xmlParser = new XmlParser($xml_response);
$root = $xmlParser->GetRoot();
$data = $xmlParser->GetData();
$orderNo = $data[$root]['google-order-number']['VALUE'];
// lets retrieve the private-data
$privateData = $data[$root]['shopping-cart']['merchant-private-data']['VALUE'];
$privateData = $privateData ? self::stringToArray($privateData) : '';
list($mode, $module, $paymentProcessorID) = self::getContext($xml_response, $privateData, $orderNo, $root);
$mode = $mode ? 'test' : 'live';
require_once 'CRM/Financial/BAO/PaymentProcessor.php';
$paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($paymentProcessorID,
$mode
);
$ipn = &self::singleton($mode, $module, $paymentProcessor);
// Create new response object
$merchant_id = $paymentProcessor['user_name'];
$merchant_key = $paymentProcessor['password'];
$server_type = ($mode == 'test') ? "sandbox" : '';
$response = new GoogleResponse($merchant_id, $merchant_key,
$xml_response, $server_type
);
if (GOOGLE_DEBUG_PP) {
CRM_Core_Error::debug_var('RESPONSE-ROOT', $response->root, TRUE, TRUE, 'Google');
}
//Check status and take appropriate action
$status = $response->HttpAuthentication($headers);
switch ($root) {
case "request-received":
case "error":
case "diagnosis":
case "checkout-redirect":
case "merchant-calculation-callback":
break;
case "new-order-notification": {
$response->SendAck();
$ipn->newOrderNotify($data[$root], $privateData, $module);
break;
}
case "order-state-change-notification": {
$response->SendAck();
$new_financial_state = $data[$root]['new-financial-order-state']['VALUE'];
$new_fulfillment_order = $data[$root]['new-fulfillment-order-state']['VALUE'];
switch ($new_financial_state) {
case 'CHARGEABLE':
$amount = $ipn->getAmount($orderNo);
if ($amount) {
$response->SendChargeOrder($data[$root]['google-order-number']['VALUE'],
$amount, $message_log
);
$response->SendProcessOrder($data[$root]['google-order-number']['VALUE'],
$message_log
);
}
break;
case 'CHARGED':
case 'PAYMENT_DECLINED':
case 'CANCELLED':
$ipn->orderStateChange($new_financial_state, $data[$root], $module);
break;
case 'REVIEWING':
case 'CHARGING':
case 'CANCELLED_BY_GOOGLE':
break;
default:
break;
}
}
case "charge-amount-notification":
case "chargeback-amount-notification":
case "refund-amount-notification":
case "risk-information-notification":
$response->SendAck();
break;
default:
break;
}
}
/**
* @param $input
* @param $ids
*
* @return bool
*/
function getInput(&$input, &$ids) {
if (!$this->getBillingID($ids)) {
return FALSE;
}
$billingID = $ids['billing'];
$lookup = array("first_name" => 'contact-name',
// "last-name" not available with google (every thing in contact-name)
"last_name" => 'last_name',
"street_address-{$billingID}" => 'address1',
"city-{$billingID}" => 'city',
"state-{$billingID}" => 'region',
"postal_code-{$billingID}" => 'postal-code',
"country-{$billingID}" => 'country-code',
);
foreach ($lookup as $name => $googleName) {
$value = $dataRoot['buyer-billing-address'][$googleName]['VALUE'];
$input[$name] = $value ? $value : NULL;
}
return TRUE;
}
/**
* Converts the comma separated name-value pairs in