Array('Name' => 'Google Checkout', 'ClassName' => 'kGWGoogleCheckout', 'ClassFile' => 'google_checkout.php', 'RequireCCFields' => 0), 'ConfigFields' => Array( 'submit_url' => Array('Name' => 'Submit URL', 'Type' => 'text', 'ValueList' => '', 'Default' => 'https://checkout.google.com/api/checkout/v2'), 'merchant_id' => Array('Name' => 'Google merchant ID', 'Type' => 'text', 'ValueList' => '', 'Default' => ''), 'merchant_key' => Array('Name' => 'Google merchant key', 'Type' => 'text', 'ValueList' => '', 'Default' => ''), 'shipping_control' => Array('Name' => 'Shipping Control', 'Type' => 'select', 'ValueList' => '3=la_CreditDirect,4=la_CreditPreAuthorize', 'Default' => 3), ) ); return $data; } /** * Returns payment form submit url * * @param Array $gw_params gateway params from payment type config * @return string */ function getFormAction($gw_params) { return $gw_params['submit_url'].'/checkout/Merchant/'.$gw_params['merchant_id']; } /** * Processed input data and convets it to fields understandable by gateway * * @param Array $item_data current order fields * @param Array $tag_params additional params for gateway passed through tag * @param Array $gw_params gateway params from payment type config * @return Array */ function getHiddenFields($item_data, $tag_params, $gw_params) { $ret = Array(); $this->gwParams = $gw_params; $cart_xml = $this->getCartXML($item_data); $ret['cart'] = base64_encode($cart_xml); $ret['signature'] = base64_encode( $this->CalcHmacSha1($cart_xml, $gw_params) ); return $ret; } function getCartXML($cart_fields) { // 1. prepare shopping cart content $sql = 'SELECT * FROM '.TABLE_PREFIX.'OrderItems oi LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = oi.ProductId WHERE oi.OrderId = '.$cart_fields['OrderId']; $order_items = $this->Conn->Query($sql); /** @var kMultiLanguage $ml_formatter */ $ml_formatter = $this->Application->recallObject('kMultiLanguage'); $cart_xml = Array (); foreach ($order_items as $order_item) { $cart_xml[] = ' '.kUtil::escape($order_item['ProductName'], kUtil::ESCAPE_HTML).' '.kUtil::escape($order_item[$ml_formatter->LangFieldName('DescriptionExcerpt')], kUtil::ESCAPE_HTML).''. $this->getPriceXML('unit-price', $order_item['Price']).' '.$order_item['Quantity'].' '; } $cart_xml = ''.implode("\n", $cart_xml).''; // 2. add order identification info (for google checkout notification) $cart_xml .= ' '.$this->Application->GetSID().' '.$cart_fields['OrderId'].' '; // 3. add all shipping types (with no costs) $sql = 'SELECT Name FROM '.TABLE_PREFIX.'ShippingType WHERE Status = '.STATUS_ACTIVE; $shipping_types = $this->Conn->GetCol($sql); $shipping_xml = ''; foreach ($shipping_types as $shipping_name) { $shipping_xml .= ' 0.00 '; } $use_ssl = substr($this->gwParams['submit_url'], 0, 8) == 'https://' ? true : null; $shipping_url = $this->getNotificationUrl('units/gateways/gw_classes/notify_scripts/google_checkout_shippings.php', $use_ssl); $shipping_xml = ' '.$shipping_xml.' '.$shipping_url.' '; $xml = ' '.$cart_xml.' '.$shipping_xml.' '; return $xml; } /** * Returns price formatted as xml tag * * @param string $tag_name * @param float $price * @return string */ function getPriceXML($tag_name, $price) { $currency = $this->Application->RecallVar('curr_iso'); return '<'.$tag_name.' currency="'.$currency.'">'.sprintf('%.2f', $price).''; } /** * Calculates the cart's hmac-sha1 signature, this allows google to verify * that the cart hasn't been tampered by a third-party. * * {@link http://code.google.com/apis/checkout/developer/index.html#create_signature} * * @param string $data the cart's xml * @return string the cart's signature (in binary format) */ function CalcHmacSha1($data, $gw_params) { $key = $gw_params['merchant_key']; $blocksize = 64; $hashfunc = 'sha1'; if (mb_strlen($key) > $blocksize) { $key = pack('H*', $hashfunc($key)); } $key = str_pad($key, $blocksize, chr(0x00)); $ipad = str_repeat(chr(0x36), $blocksize); $opad = str_repeat(chr(0x5c), $blocksize); $hmac = pack( 'H*', $hashfunc( ($key^$opad).pack( 'H*', $hashfunc( ($key^$ipad).$data ) ) ) ); return $hmac; } /** * Returns XML request, that GoogleCheckout posts to notification / shipping calculation scripts * * @return string */ function getRequestXML() { $xml_data = file_get_contents('php://input'); if ( $this->Application->isDebugMode() ) { $this->toLog($xml_data, 'xml_request.html'); } return $xml_data; // for debugging /*return ' 434532759516557 CHARGEABLE NEW REVIEWING NEW 2007-03-19T15:06:29.051Z ';*/ } /** * Processes notifications from google checkout * * @param Array $gw_params * @return int */ function processNotification($gw_params) { // parse xml & get order_id from there, like sella pay $this->gwParams = $gw_params; /** @var kXMLHelper $xml_helper */ $xml_helper = $this->Application->recallObject('kXMLHelper'); /** @var kXMLNode $root_node */ $root_node =& $xml_helper->Parse( $this->getRequestXML() ); $this->Application->XMLHeader(); define('DBG_SKIP_REPORTING', 1); $order_approvable = false; switch ($root_node->Name) { case 'MERCHANT-CALCULATION-CALLBACK': $xml_responce = $this->getShippingXML($root_node); break; case 'NEW-ORDER-NOTIFICATION': case 'RISK-INFORMATION-NOTIFICATION': case 'ORDER-STATE-CHANGE-NOTIFICATION': // http://code.google.com/apis/checkout/developer/Google_Checkout_XML_API_Notification_API.html#new_order_notifications list ($order_approvable, $xml_responce) = $this->getNotificationResponceXML($root_node); break; } echo $xml_responce; if ( $this->Application->isDebugMode() ) { $this->toLog($xml_responce, 'xml_responce.html'); } return $order_approvable ? 1 : 0; } /** * Writes XML requests and responces to a file * * @param string $xml_data * @param string $xml_file */ function toLog($xml_data, $xml_file) { $fp = fopen( (defined('RESTRICTED') ? RESTRICTED : FULL_PATH) . '/' . $xml_file, 'a' ); fwrite($fp, '--- ' . adodb_date('Y-m-d H:i:s') . ' ---' . "\n" . $xml_data); fclose($fp); } /** * Processes notification * * @param kXMLNode $root_node */ function getNotificationResponceXML(&$root_node) { // we can get notification type by "$root_node->Name" $order_approvable = false; switch ($root_node->Name) { case 'NEW-ORDER-NOTIFICATION': $order_approvable = $this->processNewOrderNotification($root_node); break; case 'RISK-INFORMATION-NOTIFICATION': $order_approvable = $this->processRiskInformationNotification($root_node); break; case 'ORDER-STATE-CHANGE-NOTIFICATION': $order_approvable = $this->processOrderStateChangeNotification($root_node); break; } // !!! globally set order id, so gw_responce.php will not fail in setting TransactionStatus // 1. receive new order notification // put address & payment type in our order using id found in merchant-private-data (Make order status: Incomplete) // 2. receive risk information // don't know what to do, just mark order some how (Make order status: Incomplete) // 3. receive status change notification to CHARGEABLE (Make order status: Pending) // only mark order status // 4. admin approves order // make api call, that changes order state (fulfillment-order-state) to PROCESSING or DELIVERED (see manual) // 5. admin declines order // make api call, that changes order state (fulfillment-order-state) to WILL_NOT_DELIVER // Before you ship the items in an order, you should ensure that you have already received the new order notification for the order, // the risk information notification for the order and an order state change notification informing you that the order's financial // state has been updated to CHARGEABLE return Array ($order_approvable, ''); } /** * Returns shipping calculations and places part of shipping address into order (1st step) * * http://code.google.com/apis/checkout/developer/Google_Checkout_XML_API_Merchant_Calculations_API.html#Returning_Merchant_Calculation_Results * * @param kXMLNode $node * @return string */ function getShippingXML(&$root_node) { // 1. extract data from xml $search_nodes = Array ( 'SHOPPING-CART:MERCHANT-PRIVATE-DATA', 'CALCULATE:ADDRESSES:ANONYMOUS-ADDRESS', 'CALCULATE:SHIPPING', ); foreach ($search_nodes as $search_string) { /** @var kXMLNode $found_node */ $found_node =& $root_node; $search_string = explode(':', $search_string); foreach ($search_string as $search_node) { $found_node =& $found_node->FindChild($search_node); } $node_data = Array (); /** @var kXMLNode $sub_node */ $sub_node =& $found_node->firstChild; do { if ($found_node->Name == 'SHIPPING') { $node_data[] = $sub_node->Attributes['NAME']; } else { $node_data[$sub_node->Name] = $sub_node->Data; } } while ( ($sub_node =& $sub_node->NextSibling()) ); switch ($found_node->Name) { case 'MERCHANT-PRIVATE-DATA': $order_id = $node_data['ORDER_ID']; $session_id = $node_data['SESSION_ID']; break; case 'ANONYMOUS-ADDRESS': $address_info = $node_data; $address_id = $found_node->Attributes['ID']; break; case 'SHIPPING': $process_shippings = $node_data; break; } } // 2. update shipping address in order /** @var OrdersItem $order */ $order = $this->Application->recallObject('ord', null, Array ('skip_autoload' => true)); $order->Load($order_id); $shipping_address = Array ( 'ShippingCity' => $address_info['CITY'], 'ShippingState' => $address_info['REGION'], 'ShippingZip' => $address_info['POSTAL-CODE'], ); /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); $shipping_address['ShippingCountry'] = $cs_helper->getCountryIso($address_info['COUNTRY-CODE'], true); $order->SetDBFieldsFromHash($shipping_address); $order->Update(); // 3. get shipping rates based on given address $shipping_types_xml = ''; $shipping_types = $this->getOrderShippings($order); // add available shipping types foreach ($shipping_types as $shipping_type) { $shipping_name = $shipping_type['ShippingName']; $processable_shipping_index = array_search($shipping_name, $process_shippings); if ($processable_shipping_index !== false) { $shipping_types_xml .= ' '.sprintf('%01.2f', $shipping_type['TotalCost']).' true '; // remove available shipping type from processable list unset($process_shippings[$processable_shipping_index]); } } // add unavailable shipping types foreach ($process_shippings as $shipping_name) { $shipping_types_xml .= ' 0.00 false '; } $shipping_types_xml = ' '.$shipping_types_xml.' '; return $shipping_types_xml; } /** * Places all information from google checkout into order (2nd step) * * @param kXMLNode $root_node */ function processNewOrderNotification(&$root_node) { // 1. extract data from xml $search_nodes = Array ( 'SHOPPING-CART:MERCHANT-PRIVATE-DATA', 'ORDER-ADJUSTMENT:SHIPPING:MERCHANT-CALCULATED-SHIPPING-ADJUSTMENT', 'BUYER-ID', 'GOOGLE-ORDER-NUMBER', 'BUYER-SHIPPING-ADDRESS', 'BUYER-BILLING-ADDRESS', ); $user_address = Array (); foreach ($search_nodes as $search_string) { /** @var kXMLNode $found_node */ $found_node =& $root_node; $search_string = explode(':', $search_string); foreach ($search_string as $search_node) { $found_node =& $found_node->FindChild($search_node); } $node_data = Array (); if ($found_node->Children) { /** @var kXMLNode $sub_node */ $sub_node =& $found_node->firstChild; do { $node_data[$sub_node->Name] = $sub_node->Data; } while ( ($sub_node =& $sub_node->NextSibling()) ); } switch ($found_node->Name) { case 'MERCHANT-PRIVATE-DATA': $order_id = $node_data['ORDER_ID']; $session_id = $node_data['SESSION_ID']; break; case 'MERCHANT-CALCULATED-SHIPPING-ADJUSTMENT': $shpipping_info = $node_data; break; case 'BUYER-ID': $buyer_id = $found_node->Data; break; case 'GOOGLE-ORDER-NUMBER': $google_order_number = $found_node->Data; break; case 'BUYER-SHIPPING-ADDRESS': $user_address['Shipping'] = $node_data; break; case 'BUYER-BILLING-ADDRESS': $user_address['Billing'] = $node_data; break; } } // 2. update shipping address in order /** @var OrdersItem $order */ $order = $this->Application->recallObject('ord', null, Array ('skip_autoload' => true)); $order->Load($order_id); if (!$order->isLoaded()) { return false; } // 2.1. this is 100% notification from google -> mark order with such payment type $order->SetDBField('PaymentType', $this->Application->GetVar('payment_type_id')); $this->parsed_responce = Array ( 'GOOGLE-ORDER-NUMBER' => $google_order_number, 'BUYER-ID' => $buyer_id ); // 2.2. save google checkout order information (maybe needed for future notification processing) $order->SetDBField('GWResult1', serialize($this->parsed_responce)); $order->SetDBField('GoogleOrderNumber', $google_order_number); // 2.3. set user-selected shipping type $shipping_types = $this->getOrderShippings($order); foreach ($shipping_types as $shipping_type) { if ($shipping_type['ShippingName'] == $shpipping_info['SHIPPING-NAME']) { $order->SetDBField('ShippingInfo', serialize(Array (1 => $shipping_type))); // minimal package number is 1 $order->SetDBField('ShippingCost', $shipping_type['TotalCost']); // set total shipping cost break; } } // 2.4. set full shipping & billing address $address_mapping = Array ( 'CONTACT-NAME' => 'To', 'COMPANY-NAME' => 'Company', 'EMAIL' => 'Email', 'PHONE' => 'Phone', 'FAX' => 'Fax', 'ADDRESS1' => 'Address1', 'ADDRESS2' => 'Address2', 'CITY' => 'City', 'REGION' => 'State', 'POSTAL-CODE' => 'Zip', ); /** @var kCountryStatesHelper $cs_helper */ $cs_helper = $this->Application->recallObject('CountryStatesHelper'); foreach ($user_address as $field_prefix => $address_details) { foreach ($address_mapping as $src_field => $dst_field) { $order->SetDBField($field_prefix.$dst_field, $address_details[$src_field]); } if (!$order->GetDBField($field_prefix.'Phone')) { $order->SetDBField($field_prefix.'Phone', '-'); // required field } $order->SetDBField( $field_prefix.'Country', $cs_helper->getCountryIso($address_details['COUNTRY-CODE'], true) ); } $order->SetDBField('OnHold', 1); $order->SetDBField('Status', ORDER_STATUS_PENDING); $order->Update(); // unlink order, that GoogleCheckout used from shopping cart on site $sql = 'DELETE FROM '.TABLE_PREFIX.'UserSessionData WHERE VariableName = "ord_id" AND VariableValue = '.$order->GetID(); $this->Conn->Query($sql); // simulate visiting shipping screen $sql = 'UPDATE '.TABLE_PREFIX.'OrderItems SET PackageNum = 1 WHERE OrderId = '.$order->GetID(); $this->Conn->Query($sql); return false; } /** * Saves risk information in order record (3rd step) * * @param kXMLNode $root_node */ function processRiskInformationNotification(&$root_node) { // 1. extract data from xml $search_nodes = Array ( 'GOOGLE-ORDER-NUMBER', 'RISK-INFORMATION', ); foreach ($search_nodes as $search_string) { /** @var kXMLNode $found_node */ $found_node =& $root_node; $search_string = explode(':', $search_string); foreach ($search_string as $search_node) { $found_node =& $found_node->FindChild($search_node); } $node_data = Array (); if ($found_node->Children) { /** @var kXMLNode $sub_node */ $sub_node =& $found_node->firstChild; do { $node_data[$sub_node->Name] = $sub_node->Data; } while ( ($sub_node =& $sub_node->NextSibling()) ); } switch ($found_node->Name) { case 'GOOGLE-ORDER-NUMBER': $google_order_number = $found_node->Data; break; case 'RISK-INFORMATION': $risk_information = $node_data; unset( $risk_information['BILLING-ADDRESS'] ); break; } } // 2. update shipping address in order /** @var OrdersItem $order */ $order = $this->Application->recallObject('ord', null, Array ('skip_autoload' => true)); $order->Load($google_order_number, 'GoogleOrderNumber'); if (!$order->isLoaded()) { return false; } // 2.1. save risk information in order $this->parsed_responce = unserialize($order->GetDBField('GWResult1')); $this->parsed_responce = array_merge_recursive($this->parsed_responce, $risk_information); $order->SetDBField('GWResult1', serialize($this->parsed_responce)); $order->Update(); return false; } /** * Perform PREAUTH/SALE type transaction direct from php script wihtout redirecting to 3rd-party website * * @param Array $item_data * @param Array $gw_params * @return bool */ function DirectPayment($item_data, $gw_params) { $this->gwParams = $gw_params; if ($gw_params['shipping_control'] == SHIPPING_CONTROL_PREAUTH) { // when shipping control is Pre-Authorize -> do nothing and charge when admin approves order return true; } $this->_chargeOrder($item_data); return false; } /** * Issue charge-order api call * * @param Array $item_data * @return bool */ function _chargeOrder($item_data) { $charge_xml = ' '.sprintf('%.2f', $item_data['TotalAmount']).' '; $root_node =& $this->executeAPICommand($charge_xml); $this->parsed_responce = unserialize($item_data['GWResult1']); if ($root_node->Name == 'REQUEST-RECEIVED') { $this->parsed_responce['FINANCIAL-ORDER-STATE'] = 'CHARGING'; return true; } return false; } /** * Perform SALE type transaction direct from php script wihtout redirecting to 3rd-party website * * @param Array $item_data * @param Array $gw_params * @return bool */ function Charge($item_data, $gw_params) { $this->gwParams = $gw_params; if ($gw_params['shipping_control'] == SHIPPING_CONTROL_DIRECT) { // when shipping control is Direct Payment -> do nothing and auto-charge on notification received return true; } $this->_chargeOrder($item_data); /** @var OrdersItem $order */ $order = $this->Application->recallObject('ord.-item', null, Array ('skip_autoload' => true)); $order->Load($item_data['OrderId']); if (!$order->isLoaded()) { return false; } $order->SetDBField('OnHold', 1); $order->Update(); return false; } /** * Executes API command for order and returns result * * @param string $command_xml * @return kXMLNode */ function &executeAPICommand($command_xml) { $submit_url = $this->gwParams['submit_url'].'/request/Merchant/'.$this->gwParams['merchant_id']; /** @var kCurlHelper $curl_helper */ $curl_helper = $this->Application->recallObject('CurlHelper'); /** @var kXMLHelper $xml_helper */ $xml_helper = $this->Application->recallObject('kXMLHelper'); $curl_helper->SetPostData($command_xml); $auth_options = Array ( CURLOPT_USERPWD => $this->gwParams['merchant_id'].':'.$this->gwParams['merchant_key'], ); $curl_helper->setOptions($auth_options); $xml_responce = $curl_helper->Send($submit_url); /** @var kXMLNode $root_node */ $root_node =& $xml_helper->Parse($xml_responce); return $root_node; } /** * Marks order as pending, when it's google status becomes CHARGEABLE (4th step) * * @param kXMLNode $root_node */ function processOrderStateChangeNotification(&$root_node) { // 1. extract data from xml $search_nodes = Array ( 'GOOGLE-ORDER-NUMBER', 'NEW-FINANCIAL-ORDER-STATE', 'PREVIOUS-FINANCIAL-ORDER-STATE', ); $order_state = Array (); foreach ($search_nodes as $search_string) { /** @var kXMLNode $found_node */ $found_node =& $root_node; $search_string = explode(':', $search_string); foreach ($search_string as $search_node) { $found_node =& $found_node->FindChild($search_node); } switch ($found_node->Name) { case 'GOOGLE-ORDER-NUMBER': $google_order_number = $found_node->Data; break; case 'NEW-FINANCIAL-ORDER-STATE': $order_state['new'] = $found_node->Data; break; case 'PREVIOUS-FINANCIAL-ORDER-STATE': $order_state['old'] = $found_node->Data; break; } } // 2. update shipping address in order /** @var OrdersItem $order */ $order = $this->Application->recallObject('ord', null, Array ('skip_autoload' => true)); $order->Load($google_order_number, 'GoogleOrderNumber'); if (!$order->isLoaded()) { return false; } $state_changed = ($order_state['old'] != $order_state['new']); if ($state_changed) { $order_charged = ($order_state['new'] == 'CHARGED') && ($order->GetDBField('Status') == ORDER_STATUS_PENDING); $this->parsed_responce = unserialize($order->GetDBField('GWResult1')); $this->parsed_responce['FINANCIAL-ORDER-STATE'] = $order_state['new']; $order->SetDBField('GWResult1', serialize($this->parsed_responce)); if ($order_charged) { // when using Pre-Authorize $order->SetDBField('OnHold', 0); } $order->Update(); if ($order_charged) { // when using Pre-Authorize /** @var OrdersEventHandler $order_eh */ $order_eh = $this->Application->recallObject('ord_EventHandler'); $order_eh->SplitOrder( new kEvent('ord:OnMassOrderApprove'), $order); } } // update order record in "google_checkout_notify.php" only when such state change happens $order_chargeable = ($order_state['new'] == 'CHARGEABLE') && $state_changed; if ($order_chargeable) { if ($this->gwParams['shipping_control'] == SHIPPING_CONTROL_PREAUTH) { $order->SetDBField('OnHold', 0); $order->Update(); } $process_xml = ''; $root_node =& $this->executeAPICommand($process_xml); } return $order_chargeable; } /** * Retrieves shipping types available for given order * * @param OrdersItem $order * @return Array */ function getOrderShippings(&$order) { $weight_sql = 'IF(oi.Weight IS NULL, 0, oi.Weight * oi.Quantity)'; $query = ' SELECT SUM(oi.Quantity) AS TotalItems, SUM('.$weight_sql.') AS TotalWeight, SUM(oi.Price * oi.Quantity) AS TotalAmount, SUM(oi.Quantity) - SUM(IF(p.MinQtyFreePromoShipping > 0 AND p.MinQtyFreePromoShipping <= oi.Quantity, oi.Quantity, 0)) AS TotalItemsPromo, SUM('.$weight_sql.') - SUM(IF(p.MinQtyFreePromoShipping > 0 AND p.MinQtyFreePromoShipping <= oi.Quantity, '.$weight_sql.', 0)) AS TotalWeightPromo, SUM(oi.Price * oi.Quantity) - SUM(IF(p.MinQtyFreePromoShipping > 0 AND p.MinQtyFreePromoShipping <= oi.Quantity, oi.Price * oi.Quantity, 0)) AS TotalAmountPromo FROM '.TABLE_PREFIX.'OrderItems oi LEFT JOIN '.TABLE_PREFIX.'Products p ON oi.ProductId = p.ProductId WHERE oi.OrderId = '.$order->GetID().' AND p.Type = 1'; $shipping_totals = $this->Conn->GetRow($query); $this->Application->recallObject('ShippingQuoteEngine'); /** @var ShippingQuoteCollector $quote_engine_collector */ $quote_engine_collector = $this->Application->recallObject('ShippingQuoteCollector'); $shipping_quote_params = Array( 'dest_country' => $order->GetDBField('ShippingCountry'), 'dest_state' => $order->GetDBField('ShippingState'), 'dest_postal' => $order->GetDBField('ShippingZip'), 'dest_city' => $order->GetDBField('ShippingCity'), 'dest_addr1' => '', 'dest_addr2' => '', 'dest_name' => 'user-' . $order->GetDBField('PortalUserId'), 'packages' => Array( Array( 'package_key' => 'package1', 'weight' => $shipping_totals['TotalWeight'], 'weight_unit' => 'KG', 'length' => '', 'width' => '', 'height' => '', 'dim_unit' => 'IN', 'packaging' => 'BOX', 'contents' => 'OTR', 'insurance' => '0' ), ), 'amount' => $shipping_totals['TotalAmount'], 'items' => $shipping_totals['TotalItems'], 'limit_types' => serialize(Array ('ANY')), 'promo_params' => Array ( 'items' => $shipping_totals['TotalItemsPromo'], 'amount' => $shipping_totals['TotalAmountPromo'], 'weight' => $shipping_totals['TotalWeightPromo'], ), ); return $quote_engine_collector->GetShippingQuotes($shipping_quote_params); } /** * Returns gateway responce from last operation * * @return string */ function getGWResponce() { return serialize($this->parsed_responce); } /** * Informs payment gateway, that order has been shipped * * http://code.google.com/apis/checkout/developer/Google_Checkout_XML_API_Order_Level_Shipping.html#Deliver_Order * * @param Array $item_data * @param Array $gw_params * @return bool */ function OrderShipped($item_data, $gw_params) { $this->gwParams = $gw_params; $shipping_info = unserialize($item_data['ShippingInfo']); if (getArrayValue($shipping_info, 'Code')) { $traking_carrier = ''.$item_data['Code'].''; } if ($item_data['ShippingTracking']) { $tracking_data = ''.$traking_carrier.' '.$item_data['ShippingTracking'].' '; } $ship_xml = ' '.$traking_data.' true '; $root_node =& $this->executeAPICommand($ship_xml); } /** * Informs payment gateway, that order has been declined * * @param Array $item_data * @param Array $gw_params * @return bool */ function OrderDeclined($item_data, $gw_params) { } }