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