add CRM/Core/CodeGen comment blocks
[civicrm-core.git] / CRM / Core / Payment / GoogleIPN.php
CommitLineData
6a488035
TO
1<?php
2
3/**
4 * Copyright (C) 2006 Google Inc.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19/* This is the response handler code that will be invoked every time
20 * a notification or request is sent by the Google Server
21 *
22 * To allow this code to receive responses, the url for this file
23 * must be set on the seller page under Settings->Integration as the
24 * "API Callback URL'
25 * Order processing commands can be sent automatically by placing these
26 * commands appropriately
27 *
28 * To use this code for merchant-calculated feedback, this url must be
29 * set also as the merchant-calculations-url when the cart is posted
30 * Depending on your calculations for shipping, taxes, coupons and gift
31 * certificates update parts of the code as required
32 *
33 */
34
35
36
37define('GOOGLE_DEBUG_PP', 0);
28518c90
EM
38
39/**
40 * Class CRM_Core_Payment_GoogleIPN
41 */
6a488035
TO
42class CRM_Core_Payment_GoogleIPN extends CRM_Core_Payment_BaseIPN {
43
44 /**
45 * We only need one instance of this object. So we use the singleton
46 * pattern and cache the instance in this variable
47 *
48 * @var object
49 * @static
50 */
51 static private $_singleton = NULL;
52
53 /**
54 * mode of operation: live or test
55 *
56 * @var object
57 */
58 protected $_mode = NULL;
59
60 static function retrieve($name, $type, $object, $abort = TRUE) {
61 $value = CRM_Utils_Array::value($name, $object);
62 if ($abort && $value === NULL) {
63 CRM_Core_Error::debug_log_message("Could not find an entry for $name");
64 echo "Failure: Missing Parameter<p>";
65 exit();
66 }
67
68 if ($value) {
69 if (!CRM_Utils_Type::validate($value, $type)) {
70 CRM_Core_Error::debug_log_message("Could not find a valid entry for $name");
71 echo "Failure: Invalid Parameter<p>";
72 exit();
73 }
74 }
75
76 return $value;
77 }
78
79 /**
80 * Constructor
81 *
82 * @param string $mode the mode of operation: live or test
83 *
77b97be7
EM
84 * @param $paymentProcessor
85 *
86 * @return \CRM_Core_Payment_GoogleIPN
6a488035
TO
87 */
88 function __construct($mode, &$paymentProcessor) {
89 parent::__construct();
90
91 $this->_mode = $mode;
92 $this->_paymentProcessor = $paymentProcessor;
93 }
94
95 /**
96 * The function gets called when a new order takes place.
97 *
dd244018 98 * @param xml $dataRoot response send by google in xml format
6a488035
TO
99 * @param array $privateData contains the name value pair of <merchant-private-data>
100 *
dd244018 101 * @param $component
6a488035 102 *
dd244018 103 * @return void
6a488035
TO
104 */
105 function newOrderNotify($dataRoot, $privateData, $component) {
106 $ids = $input = $params = array();
107
108 $input['component'] = strtolower($component);
109
110 $ids['contact'] = self::retrieve('contactID', 'Integer', $privateData, TRUE);
111 $ids['contribution'] = self::retrieve('contributionID', 'Integer', $privateData, TRUE);
112
113 $ids['contributionRecur'] = $ids['contributionPage'] = NULL;
114 if ($input['component'] == "event") {
115 $ids['event'] = self::retrieve('eventID', 'Integer', $privateData, TRUE);
116 $ids['participant'] = self::retrieve('participantID', 'Integer', $privateData, TRUE);
117 $ids['membership'] = NULL;
118 }
119 else {
120 $ids['membership'] = self::retrieve('membershipID', 'Integer', $privateData, FALSE);
121 $ids['related_contact'] = self::retrieve('relatedContactID', 'Integer', $privateData, FALSE);
122 $ids['onbehalf_dupe_alert'] = self::retrieve('onBehalfDupeAlert', 'Integer', $privateData, FALSE);
123 $ids['contributionRecur'] = self::retrieve('contributionRecurID', 'Integer', $privateData, FALSE);
124 }
125
126 $paymentProcessorID = CRM_Core_DAO::getFieldValue(
127 'CRM_Financial_DAO_PaymentProcessorType',
128 'Google_Checkout',
129 'id',
130 'payment_processor_type'
131 );
132
133 if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
134 return FALSE;
135 }
136
137 $input['invoice'] = $privateData['invoiceID'];
138 $input['newInvoice'] = $dataRoot['google-order-number']['VALUE'];
139
140 if ($ids['contributionRecur']) {
141 if ($objects['contributionRecur']->invoice_id == $dataRoot['serial-number']) {
142 CRM_Core_Error::debug_log_message("The new order notification already handled: {$dataRoot['serial-number']}.");
143 return;
144 }
145 else {
146 $transaction = new CRM_Core_Transaction();
147
148 CRM_Core_Error::debug_log_message("New order for an installment received.");
149 $recur = &$objects['contributionRecur'];
150
151 // fix dates that already exist
152 $dates = array('create', 'start', 'end', 'cancel', 'modified');
153 foreach ($dates as $date) {
154 $name = "{$date}_date";
155 if ($recur->$name) {
156 $recur->$name = CRM_Utils_Date::isoToMysql($recur->$name);
157 }
158 }
159 $recur->invoice_id = $dataRoot['serial-number'];
160 $recur->processor_id = $input['newInvoice'];
161 $recur->save();
162
163 if ($objects['contribution']->contribution_status_id == 1) {
164 // create a contribution and then get it processed
165 $contribution = new CRM_Contribute_DAO_Contribution();
166 $contribution->contact_id = $ids['contact'];
167 $contribution->financial_type_id = $objects['contributionType']->id;
168 $contribution->contribution_page_id = $objects['contribution']->contribution_page_id;
169 $contribution->contribution_recur_id = $ids['contributionRecur'];
170 $contribution->receive_date = date('YmdHis');
171 $contribution->currency = $objects['contribution']->currency;
172 $contribution->payment_instrument_id = $objects['contribution']->payment_instrument_id;
173 $contribution->amount_level = $objects['contribution']->amount_level;
174 $contribution->address_id = $objects['contribution']->address_id;
175 $contribution->invoice_id = $input['invoice'];
176 $contribution->total_amount = $dataRoot['order-total']['VALUE'];
177 $contribution->contribution_status_id = 2;
94d1bc8d
PJ
178 $contribution->campaign_id = $objects['contribution']->campaign_id;
179
6a488035
TO
180 $objects['contribution'] = $contribution;
181 }
182 $transaction->commit();
183 }
184 }
185
186 // make sure the invoice is valid and matches what we have in the contribution record
187 $contribution = &$objects['contribution'];
188
189 if ($contribution->invoice_id != $input['invoice']) {
190 CRM_Core_Error::debug_log_message("Invoice values dont match between database and IPN request");
191 return;
192 }
193
194 // lets replace invoice-id with google-order-number because thats what is common and unique
195 // in subsequent calls or notifications sent by google.
196 $contribution->invoice_id = $input['newInvoice'];
197
198 $input['amount'] = $dataRoot['order-total']['VALUE'];
199
200 if ($contribution->total_amount != $input['amount']) {
201 CRM_Core_Error::debug_log_message("Amount values dont match between database and IPN request");
202 return;
203 }
204
205 if (!$this->getInput($input, $ids, $dataRoot)) {
206 return FALSE;
207 }
208
209 $transaction = new CRM_Core_Transaction();
210
211 // check if contribution is already completed, if so we ignore this ipn
212 if ($contribution->contribution_status_id == 1) {
213 CRM_Core_Error::debug_log_message("returning since contribution has already been handled");
214 return;
215 }
216 else {
217 /* Since trxn_id hasn't got any use here,
218 * lets make use of it by passing the eventID/membershipTypeID to next level.
219 * And change trxn_id to google-order-number before finishing db update */
220
a7488080 221 if (!empty($ids['event'])) {
6a488035
TO
222 $contribution->trxn_id = $ids['event'] . CRM_Core_DAO::VALUE_SEPARATOR . $ids['participant'];
223 }
a7488080 224 elseif (!empty($ids['membership'])) {
6a488035
TO
225 $contribution->trxn_id = $ids['membership'][0] . CRM_Core_DAO::VALUE_SEPARATOR . $ids['related_contact'] . CRM_Core_DAO::VALUE_SEPARATOR . $ids['onbehalf_dupe_alert'];
226 }
227 }
228
229 // CRM_Core_Error::debug_var( 'c', $contribution );
230 $contribution->save();
231 $transaction->commit();
232
233 return TRUE;
234 }
235
236 /**
237 * The function gets called when the state(CHARGED, CANCELLED..) changes for an order
238 *
fd31fa4c
EM
239 * @param string $status status of the transaction send by google
240 * @param $dataRoot
241 * @param array $privateData contains the name value pair of <merchant-private-data>
6a488035 242 *
fd31fa4c 243 * @param $component
6a488035 244 *
fd31fa4c 245 * @return void
6a488035
TO
246 */
247 function orderStateChange($status, $dataRoot, $privateData, $component) {
248 $input = $objects = $ids = array();
249 $input['component'] = strtolower($component);
250
251 $ids['contributionRecur'] = self::retrieve('contributionRecurID', 'Integer', $privateData, FALSE);
252 $serial = $dataRoot['serial-number'];
253 $orderNo = $dataRoot['google-order-number']['VALUE'];
254
255 $contribution = new CRM_Contribute_BAO_Contribution();
256 $contribution->invoice_id = $orderNo;
257
258 if (!$contribution->find(TRUE)) {
259 CRM_Core_Error::debug_log_message("orderStateChange: Could not find contribution record with invoice id: $serial");
260 return;
261 }
262
263 // Google sends the charged notification twice.
264 // So to make sure, code is not executed again.
265 if ($contribution->contribution_status_id == 1) {
266 CRM_Core_Error::debug_log_message("Contribution already handled (ContributionID = {$contribution->id}).");
267 return;
268 }
269
270 // make sure invoice is set to serial no for recurring payments, to avoid violating uniqueness
271 $contribution->invoice_id = $ids['contributionRecur'] ? $serial : $orderNo;
272
273 $objects['contribution'] = &$contribution;
274 $ids['contribution'] = $contribution->id;
275 $ids['contact'] = $contribution->contact_id;
276
277 $ids['event'] = $ids['participant'] = $ids['membership'] = NULL;
278 $ids['contributionPage'] = NULL;
279
280 if ($input['component'] == "event") {
281 list($ids['event'], $ids['participant']) = explode(CRM_Core_DAO::VALUE_SEPARATOR,
282 $contribution->trxn_id
283 );
284 }
285 else {
286 $ids['related_contact'] = NULL;
287 $ids['onbehalf_dupe_alert'] = NULL;
288 if ($contribution->trxn_id) {
289 list($ids['membership'], $ids['related_contact'], $ids['onbehalf_dupe_alert']) = explode(CRM_Core_DAO::VALUE_SEPARATOR,
290 $contribution->trxn_id
291 );
292 }
293 foreach (array(
294 'membership', 'related_contact', 'onbehalf_dupe_alert') as $fld) {
295 if (!is_numeric($ids[$fld])) {
296 unset($ids[$fld]);
297 }
298 }
299 }
300
301 $paymentProcessorID = CRM_Core_DAO::getFieldValue(
302 'CRM_Financial_DAO_PaymentProcessorType',
303 'Google_Checkout',
304 'id',
305 'payment_processor_type'
306 );
307
308 $this->loadObjects($input, $ids, $objects, TRUE, $paymentProcessorID);
309
310 $transaction = new CRM_Core_Transaction();
311
312 // CRM_Core_Error::debug_var( 'c', $contribution );
313 if ($status == 'PAYMENT_DECLINED' ||
314 $status == 'CANCELLED_BY_GOOGLE' ||
315 $status == 'CANCELLED'
316 ) {
317 return $this->failed($objects, $transaction);
318 }
319
320 $input['amount'] = $contribution->total_amount;
321 $input['fee_amount'] = NULL;
322 $input['net_amount'] = NULL;
323 $input['trxn_id'] = $ids['contributionRecur'] ? $serial : $dataRoot['google-order-number']['VALUE'];
324 $input['is_test'] = $contribution->is_test;
325
326 $recur = NULL;
327 if ($ids['contributionRecur']) {
328 $recur = $objects['contributionRecur'];
329 }
330 $this->completeTransaction($input, $ids, $objects, $transaction, $recur);
331
332 $this->completeRecur($input, $ids, $objects);
333 }
334
335 function completeRecur($input, $ids, $objects) {
336 if ($ids['contributionRecur']) {
337 $recur = &$objects['contributionRecur'];
338 $contributionCount = CRM_Core_DAO::singleValueQuery("
339SELECT count(*)
340FROM civicrm_contribution
341WHERE contribution_recur_id = {$ids['contributionRecur']}
342");
343 $autoRenewMembership = FALSE;
344 if ($recur->id &&
345 isset($ids['membership']) &&
346 $ids['membership']
347 ) {
348 $autoRenewMembership = TRUE;
349 }
350 if ($recur->installments && ($contributionCount >= $recur->installments)) {
351 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
352
353 $recur->create_date = CRM_Utils_Date::isoToMysql($recur->create_date);
354 $recur->start_date = CRM_Utils_Date::isoToMysql($recur->start_date);
355 $recur->cancel_date = CRM_Utils_Date::isoToMysql($recur->cancel_date);
356 $recur->end_date = date('YmdHis');
357 $recur->modified_date = date('YmdHis');
358 $recur->contribution_status_id = array_search('Completed', $contributionStatus);
359 $recur->trnx_id = $dataRoot['google-order-number']['VALUE'];
360 $recur->save();
361
362 //send recurring Notification email for user
363 CRM_Contribute_BAO_ContributionPage::recurringNotify(
364 CRM_Core_Payment::RECURRING_PAYMENT_END,
365 $ids['contact'],
366 $ids['contributionPage'],
367 $recur,
368 $autoRenewMembership
369 );
370 }
371 elseif ($contributionCount == 1) {
372 CRM_Contribute_BAO_ContributionPage::recurringNotify(
373 CRM_Core_Payment::RECURRING_PAYMENT_START,
374 $ids['contact'],
375 $ids['contributionPage'],
376 $recur,
377 $autoRenewMembership
378 );
379 }
380 }
381 }
382
383 /**
384 * singleton function used to manage this object
385 *
386 * @param string $mode the mode of operation: live or test
387 *
2a6da8d7
EM
388 * @param $component
389 * @param $paymentProcessor
390 *
6a488035
TO
391 * @return object
392 * @static
393 */
394 static function &singleton($mode, $component, &$paymentProcessor) {
395 if (self::$_singleton === NULL) {
396 self::$_singleton = new CRM_Core_Payment_GoogleIPN($mode, $paymentProcessor);
397 }
398 return self::$_singleton;
399 }
400
401 /**
402 * The function retrieves the amount the contribution is for, based on the order-no google sends
403 *
404 * @param int $orderNo <order-total> send by google
405 *
406 * @return amount
407 * @access public
408 */
409 function getAmount($orderNo) {
410 $contribution = new CRM_Contribute_DAO_Contribution();
411 $contribution->invoice_id = $orderNo;
412 if (!$contribution->find(TRUE)) {
413 CRM_Core_Error::debug_log_message("getAmount: Could not find contribution record with invoice id: $orderNo");
414 echo "Failure: Could not find contribution record with invoice id: $orderNo <p>";
415 exit();
416 }
417 return $contribution->total_amount;
418 }
419
420 /**
421 * The function returns the component(Event/Contribute..), given the google-order-no and merchant-private-data
422 *
2a6da8d7
EM
423 * @param array $privateData contains the name value pair of <merchant-private-data>
424 * @param int $orderNo <order-total> send by google
425 * @param string $root root of xml-response
6a488035 426 *
2a6da8d7
EM
427 * @param $response
428 * @param $serial
429 * @internal param \xml $xml_response response send by google in xml format
6a488035
TO
430 * @return array context of this call (test, module, payment processor id)
431 * @static
432 */
433 function getContext($privateData, $orderNo, $root, $response, $serial) {
434 $contributionID = CRM_Utils_Array::value('contributionID', $privateData);
435 $contribution = new CRM_Contribute_DAO_Contribution();
436 if ($root == 'new-order-notification') {
437 $contribution->id = $contributionID;
438 }
439 else {
440 $contribution->invoice_id = $orderNo;
441 }
442 if (!$contribution->find(TRUE)) {
443 CRM_Core_Error::debug_log_message("getContext: Could not find contribution record with invoice id: $orderNo");
444 $response->SendAck($serial);
445 }
446
447 $module = 'Contribute';
448 if (stristr($contribution->source, ts('Online Contribution'))) {
449 $module = 'Contribute';
450 }
451 elseif (stristr($contribution->source, ts('Online Event Registration'))) {
452 $module = 'Event';
453 }
454 $isTest = $contribution->is_test;
455
456 $ids = $input = $objects = array();
457 $objects['contribution'] = &$contribution;
458 $ids['contributionRecur'] = self::retrieve('contributionRecurID', 'Integer', $privateData, FALSE);
459 $input['component'] = strtolower($module);
460
461 if (!$ids['contributionRecur'] && $contribution->contribution_status_id == 1) {
462 CRM_Core_Error::debug_log_message("Contribution already handled (ContributionID = {$contribution->id}).");
463 // There is no point in going further. Return ack so we don't receive the same ipn.
464 $response->SendAck($serial);
465 }
466
467 if ($input['component'] == 'event') {
468 if ($root == 'new-order-notification') {
469 $ids['event'] = $privateData['eventID'];
470 }
471 else {
472 list($ids['event'], $ids['participant']) =
473 explode(CRM_Core_DAO::VALUE_SEPARATOR, $contribution->trxn_id);
474 }
475 }
476
477 $paymentProcessorID = CRM_Core_DAO::getFieldValue(
478 'CRM_Financial_DAO_PaymentProcessor',
479 'Google_Checkout',
480 'id',
481 'payment_processor_type'
482 );
483
484 $this->loadObjects($input, $ids, $objects, FALSE, $paymentProcessorID);
485
486 if (!$ids['paymentProcessor']) {
487 CRM_Core_Error::debug_log_message("Payment processor could not be retrieved.");
488 // There is no point in going further. Return ack so we don't receive the same ipn.
489 $response->SendAck($serial);
490 }
491
492 return array($isTest, $input['component'], $ids['paymentProcessor']);
493 }
494
495 /**
496 * This method is handles the response that will be invoked (from extern/googleNotify) every time
497 * a notification or request is sent by the Google Server.
498 *
499 */
500 static function main($xml_response) {
501 require_once 'Google/library/googleresponse.php';
502 require_once 'Google/library/googlerequest.php';
503 require_once 'Google/library/googlemerchantcalculations.php';
504 require_once 'Google/library/googleresult.php';
505 require_once 'Google/library/xml-processing/gc_xmlparser.php';
506
507 $config = CRM_Core_Config::singleton();
508
509 // Retrieve the XML sent in the HTTP POST request to the ResponseHandler
510 if (get_magic_quotes_gpc()) {
511 $xml_response = stripslashes($xml_response);
512 }
513
514 $headers = CRM_Utils_System::getAllHeaders();
515
516 if (GOOGLE_DEBUG_PP) {
517 CRM_Core_Error::debug_var('RESPONSE', $xml_response, TRUE, TRUE, 'Google');
518 }
519
520 // Retrieve the root and data from the xml response
521 $response = new GoogleResponse();
522 list($root, $data) = $response->GetParsedXML($xml_response);
523 // lets retrieve the private-data & order-no
524 $privateData = NULL;
525 if (array_key_exists('shopping-cart', $data[$root])) {
526 $privateData = $data[$root]['shopping-cart']['merchant-private-data']['VALUE'];
527 }
528 if (empty($privateData) && array_key_exists('order-summary', $data[$root])
529 && array_key_exists('shopping-cart', $data[$root]['order-summary'])) {
530 $privateData = $data[$root]['order-summary']['shopping-cart']['merchant-private-data']['VALUE'];
531 }
532 $privateData = $privateData ? self::stringToArray($privateData) : '';
533 $orderNo = $data[$root]['google-order-number']['VALUE'];
534 $serial = $data[$root]['serial-number'];
535
536 // a dummy object to call get context and a parent function inside it.
537 $ipn = new CRM_Core_Payment_GoogleIPN('live', $dummyProcessor);
538 list($mode, $module, $paymentProcessorID) = $ipn->getContext($privateData, $orderNo, $root, $response, $serial);
539 $mode = $mode ? 'test' : 'live';
540
541 $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($paymentProcessorID, $mode);
542 $merchant_id = $paymentProcessor['user_name'];
543 $merchant_key = $paymentProcessor['password'];
544 $response->SetMerchantAuthentication($merchant_id, $merchant_key);
545
546 $server_type = ($mode == 'test') ? 'sandbox' : 'production';
547 $request = new GoogleRequest($merchant_id, $merchant_key, $server_type);
548
549 $ipn = self::singleton($mode, $module, $paymentProcessor);
550
551 if (GOOGLE_DEBUG_PP) {
552 CRM_Core_Error::debug_var('RESPONSE-ROOT', $response->root, TRUE, TRUE, 'Google');
553 }
554
555 //Check status and take appropriate action
556 $status = $response->HttpAuthentication($headers);
557
558 switch ($root) {
559 case "request-received":
560 case "error":
561 case "diagnosis":
562 case "checkout-redirect":
563 case "merchant-calculation-callback":
564 break;
565
566 case "new-order-notification": {
567 $response->SendAck($serial, FALSE);
568 $ipn->newOrderNotify($data[$root], $privateData, $module);
569 break;
570 }
571
572 case "order-state-change-notification": {
573 $response->SendAck($serial, FALSE);
574 $new_financial_state = $data[$root]['new-financial-order-state']['VALUE'];
575 $new_fulfillment_order = $data[$root]['new-fulfillment-order-state']['VALUE'];
576
577 switch ($new_financial_state) {
578 case 'CHARGEABLE':
579 break;
580
581 case 'CHARGED':
582 case 'PAYMENT_DECLINED':
583 case 'CANCELLED':
584 case 'CANCELLED_BY_GOOGLE':
585 $ipn->orderStateChange($new_financial_state, $data[$root], $privateData, $module);
586 break;
587
588 case 'REVIEWING':
589 case 'CHARGING':
590 break;
591
592 default:
593 break;
594 }
595 break;
596 }
597
598 case "authorization-amount-notification": {
599 $response->SendAck($serial, FALSE);
600 $new_financial_state = $data[$root]['order-summary']['financial-order-state']['VALUE'];
601 $new_fulfillment_order = $data[$root]['order-summary']['fulfillment-order-state']['VALUE'];
602
603 switch ($new_financial_state) {
604 case 'CHARGEABLE':
605 // For google-handled subscriptions chargeorder needn't be initiated,
606 // assuming auto-charging is turned on.
607 //$request->SendProcessOrder($data[$root]['google-order-number']['VALUE']);
608 //$request->SendChargeOrder($data[$root]['google-order-number']['VALUE'],'');
609 break;
610
611 case 'CHARGED':
612 case 'PAYMENT_DECLINED':
613 case 'CANCELLED':
614 break;
615
616 case 'REVIEWING':
617 case 'CHARGING':
618 case 'CANCELLED_BY_GOOGLE':
619 break;
620
621 default:
622 break;
623 }
624 break;
625 }
626
627 case "charge-amount-notification":
628 case "chargeback-amount-notification":
629 case "refund-amount-notification":
630 case "risk-information-notification":
631 $response->SendAck($serial);
632 break;
633
634 default:
635 break;
636 }
637 }
638
639 function getInput(&$input, &$ids, $dataRoot) {
640 if (!$this->getBillingID($ids)) {
641 return FALSE;
642 }
643
644 $billingID = $ids['billing'];
645 $lookup = array(
646 "first_name" => 'contact-name',
647 // "last-name" not available with google (every thing in contact-name)
648 "last_name" => 'last_name',
649 "street_address-{$billingID}" => 'address1',
650 "city-{$billingID}" => 'city',
651 "state-{$billingID}" => 'region',
652 "postal_code-{$billingID}" => 'postal-code',
653 "country-{$billingID}" => 'country-code',
654 );
655
656 foreach ($lookup as $name => $googleName) {
657 if (array_key_exists($googleName, $dataRoot['buyer-billing-address'])) {
658 $value = $dataRoot['buyer-billing-address'][$googleName]['VALUE'];
659 }
660 $input[$name] = $value ? $value : NULL;
661 }
662 return TRUE;
663 }
664
665 /**
666 * Converts the comma separated name-value pairs in <merchant-private-data>
667 * to an array of name-value pairs.
668 */
669 static function stringToArray($str) {
670 $vars = $labels = array();
671 $labels = explode(',', $str);
672 foreach ($labels as $label) {
673 $terms = explode('=', $label);
674 $vars[$terms[0]] = $terms[1];
675 }
676 return $vars;
677 }
678}
679