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