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