1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8class CCLiteLib extends TikiDb_Bridge
9{
10	// member vars (defaults from prefs)
11	private $gateway;
12	private $key_hash;
13	private $registries;
14	private $currencies;
15	private $merchant_user;
16
17	function __construct()
18	{
19		global $prefs;
20		$access = TikiLib::lib('access');
21		$access->check_feature('payment_feature');
22
23		// need to add a check for empty, not just y/n - TODO one day
24		//$access->check_feature('payment_cclite_registries');
25		//$access->check_feature('payment_cclite_gateway');
26		//$access->check_feature('payment_cclite_merchant_key');
27
28		$this->gateway = rtrim($prefs['payment_cclite_gateway'], '/');
29		$this->registries = unserialize($prefs['payment_cclite_registries']);
30		$this->currencies = unserialize($prefs['payment_cclite_currencies']);
31		$this->merchant_user = $prefs['payment_cclite_merchant_user'];
32
33		if (($prefs['payment_cclite_mode'] == 'test' && $_SERVER['SERVER_ADDR'] != '127.0.0.1' && $_SERVER['SERVER_ADDR'] != '::1') ||
34					empty($prefs['payment_cclite_test_ip'])
35				) {
36			$ip = $_SERVER['SERVER_ADDR'];
37		} else {
38			// debug SERVER_ADDR for local testing on NAT'ed server
39			$ip = $prefs['payment_cclite_test_ip'];
40		}
41		$api_hash = hash($prefs['payment_cclite_hashing_algorithm'], ($prefs['payment_cclite_merchant_key'] . $ip), 'true');
42		$this->key_hash = CCLiteLib::urlsafe_b64encode($api_hash);
43	}
44
45	public function get_registries()
46	{
47		return $this->registries;
48	}
49
50	public function get_registry()
51	{
52		if (! empty($this->registries)) {
53			return $this->registries[0];	// default if not specified in plugins etc
54		} else {
55			Feedback::error(tra('No registries specified in admin/payment/cclite.'));
56		}
57	}
58
59	public function get_currencies()
60	{
61		return $this->currencies;
62	}
63
64	/**
65	 * @param string $reg Registry to find currency for (uses registries[0] if not specified)
66	 */
67	public function get_currency($reg = '')
68	{
69		global $prefs;
70
71		if (empty($reg)) {
72			$reg = $this->get_registry();
73		}
74
75		$i = array_search($reg, $this->registries);
76
77		if ($i !== false) {
78			return $this->currencies[$i];
79		} else {
80			return $prefs['payment_currency'];
81		}
82	}
83
84	public function get_invoice($ipn_data)
85	{
86		return isset($ipn_data['invoice']) ? $ipn_data['invoice'] : 0;
87	}
88
89	public function get_amount($ipn_data)
90	{
91		return $ipn_data['mc_gross'];
92	}
93
94	public function is_valid($ipn_data, $payment_info)
95	{
96		global $prefs;
97
98		if (! is_array($payment_info)) {
99			return false;
100		}
101
102		// Skip other events
103		if ($ipn_data['payment_status'] != 'Completed') {
104			return false;
105		}
106
107		// Make sure it is addressed to the right account
108		if ($ipn_data['receiver_email'] != $prefs['payment_cclite_business']) {
109			return false;
110		}
111
112		// Require same currency
113		if ($ipn_data['mc_currency'] != $payment_info['currency']) {
114			return false;
115		}
116
117		// Skip duplicate translactions
118		foreach ($payment_info['payments'] as $payment) {
119			if ($payment['type'] == 'cclite') {
120				if ($payment['details']['txn_id'] == $ipn_data['txn_id']) {
121					return false;
122				}
123			}
124		}
125
126		return true;
127	}
128
129	/**
130	 * This function just calls $paymentlib->enter_payment() which then triggers the behaviours
131	 * The behaviours the do the actual transfer of currency
132	 *
133	 * @param int $invoice
134	 * @param decimal $amount
135	 * @param string $currency
136	 * @param string $registry
137	 * @param string $source_user
138	 *
139	 * @return string result from cclite
140	 */
141	public function pay_invoice($invoice, $amount, $currency = '', $registry = '', $source_user = '')
142	{
143		global $user, $prefs;
144		$tikilib = TikiLib::lib('tiki');
145		$paymentlib = TikiLib::lib('payment');
146
147		$msg = tr('Cclite payment initiated on %0', $tikilib->get_short_datetime($tikilib->now));
148
149		$paymentlib->enter_payment($invoice, $amount, 'cclite', ['info' => $msg]);
150
151		return $msg;
152	}
153
154	/**
155	 * Pays $amount from logged in (or source_user TODO) user to manager account
156	 *
157	 * @param int $invoice
158	 * @param decimal $amount
159	 * @param string $currency
160	 * @param string $registry
161	 * @param string $destination_user
162	 *
163	 * @return string result from cclite
164	 */
165	public function pay_user($amount, $currency = '', $registry = '', $destination_user = '', $source_user = '')
166	{
167		global $user, $prefs;
168		$paymentlib = TikiLib::lib('payment');
169		if (empty($source_user)) {
170			$source_user = $this->merchant_user;
171		}
172
173		$res = $this->cclite_send_request('pay', $destination_user, $registry, $amount, $currency, $source_user);
174
175		$r = $this->cclite_send_request('logoff');
176
177		return $res;
178	}
179
180	/**
181	 * Adapted from cclite 0.7 drupal gateway (example)
182	 *
183	 * @command		recent|summary|pay|adduser|modifyuser|debit
184	 * @other_user	destination for payment - uses merchant_user if empty
185	 * @registry	cclite registry
186	 * @amount		amount (decimal/float for cost, or email for adduser command)
187	 * @currency	currency (same as currency "name" in cclite (not "code" yet)
188	 * 				defaults to registry currency
189	 * @main_user	source of payment - uses logged-in user if empty
190	 *
191	 * @return		result from cclite server (html hopefully)
192	 */
193
194	function cclite_send_request($command, $other_user = '', $registry = '', $amount = 0, $currency = '', $main_user = '')
195	{
196		global $user, $prefs;
197
198		if (empty($other_user)) {
199			$other_user = $this->merchant_user;
200		}
201
202		if (empty($main_user)) {
203			$main_user = $user;
204		}
205
206		if (empty($registry)) {
207			$registry = $this->get_registry();
208		}
209
210		if (empty($currency)) {
211			$currency = $this->get_currency($registry);
212		}
213
214		$result = '';
215
216		// construct the payment url from configuration information
217		$cclite_base_url = $this->gateway;
218		$REST_url = '';
219		$ch = curl_init();
220		if ($command != 'adduser') {
221			$logon_result = $this->cclite_remote_logon($main_user, $registry);
222			if ($logon_result[0] != 'failed' && strlen($logon_result[1])) {
223				curl_setopt($ch, CURLOPT_COOKIE, $logon_result[1]);
224			} else {
225				return tr('Connection to Cclite server %0 failed for %1<br />"%2"', $cclite_base_url, $main_user, $logon_result[1]);
226			}
227		}
228		curl_setopt($ch, CURLOPT_AUTOREFERER, true);
229		//curl_setopt($ch, CURLOPT_COOKIESESSION, true);
230		curl_setopt($ch, CURLOPT_FAILONERROR, false);
231		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
232		curl_setopt($ch, CURLOPT_FRESH_CONNECT, false);
233		curl_setopt($ch, CURLOPT_HEADER, false);
234		curl_setopt($ch, CURLOPT_POST, true);
235		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
236		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
237
238		//curl_setopt($ch, CURLOPT_VERBOSE, true);
239
240		// this switch statement needs to map to the Rewrites in the cclite .htaccess file, so if you're
241		// doing something custom-made, you need to think about:
242		// -here-, .htaccess and various bits of login in the cclite motor
243		switch ($command) {
244			case 'recent':
245				$REST_url = "$cclite_base_url/recent/transactions";
246				break;
247
248			case 'summary':
249				$REST_url = "$cclite_base_url/summary";
250				break;
251
252			case 'pay':
253				$REST_url = "$cclite_base_url/pay/$other_user/$registry/$amount/$currency";
254				break;
255
256			case 'adduser':
257				$REST_url = "$cclite_base_url/direct/adduser/$registry/" . urlencode($other_user . '/' . $amount);
258				curl_setopt($ch, CURLOPT_COOKIE, 'merchant_key_hash=' . $this->key_hash);
259				break;
260
261			case 'modifyuser':
262				// non-working at present...
263				$REST_url = "$cclite_base_url/direct/modifyuser/$registry/" . urlencode($other_user . '/' . $amount);
264				curl_setopt($ch, CURLOPT_COOKIE, 'merchant_key_hash=' . $this->key_hash);
265				break;
266
267			case 'debit':
268				// non-working at present...
269				$REST_url = "$cclite_base_url/debit/$other_user/$registry/$amount/$currency";
270				break;
271
272			case 'logoff':
273				// non-working at present...
274				$REST_url = "$cclite_base_url/logoff";
275				break;
276
277			default:
278				return "No cclite function selected use <a title=\"cclite passthrough help\" href=\"/$cclite_base_url/help\">help</a>" ;
279		}
280
281		curl_setopt($ch, CURLOPT_URL, $REST_url);
282		$result = curl_exec($ch);
283		curl_close($ch);
284		return strip_tags($result);
285	}
286
287	/**
288	 * Modified from cclite 0.7 gateway various examples
289	 *
290	 * @return multitype:mixed string |multitype:string
291	 */
292	private function cclite_remote_logon($username = '', $registry = '')
293	{
294		global $user, $prefs;
295		$userlib = TikiLib::lib('user');
296
297		if (empty($username)) {
298			$username = $user;
299		}
300
301		// not worth trying if no user name
302		if (! empty($username)) {
303			if (empty($registry)) {
304				$registry = $this->get_registry();
305			}
306			$cclite_base_url = $this->gateway;
307
308			// payment url from configuration information
309			$REST_url = "$cclite_base_url/logon/$username/$registry";	// /$api_hash
310
311			$ch = curl_init();
312			curl_setopt($ch, CURLOPT_AUTOREFERER, true);
313			curl_setopt($ch, CURLOPT_COOKIE, 'merchant_key_hash=' . $this->key_hash);
314			curl_setopt($ch, CURLOPT_COOKIESESSION, true);
315			curl_setopt($ch, CURLOPT_FAILONERROR, true);
316			curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
317			curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
318			curl_setopt($ch, CURLOPT_HEADER, true);
319			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
320			curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
321			curl_setopt($ch, CURLOPT_URL, $REST_url);
322
323//			curl_setopt($ch, CURLOPT_VERBOSE, true);
324
325			$logon = curl_exec($ch);
326			curl_close($ch);
327
328			$results = [];	// for response & cookies on success
329			$err_msg = '';		// error message on failure
330
331			if ($logon) {
332				// e.g. login failed for jonny_tiki at c2c1:  <a href="http://c2c.ourproject.org/cgi-bin/cclite.cgi">Try again?</a>
333				if (preg_match('/^(login failed for ' . $username . '.*' . $registry . '[^<]*)/mi', $logon, $results)) {	// no user there?
334					$email = $userlib->get_user_email($username);
335					if ($email) {	// required
336						$res = $this->cclite_send_request('adduser', $username, $registry, $email);	// not currently working cclite 0.7.0
337						if ($res && ! preg_match('/404 Not Found/', $res)) {							// seems to return a 404 :(
338							$logon = curl_exec($ch);	// retry login
339						} else {
340							$err_msg = trim($results[0]);
341							$logon = 'failed';
342						}
343					}
344				}
345
346				// e.g. test_user at test_reg is not active: confirm or contact the administrator <a href="http://c2c.ourproject.org/cgi-bin/cclite.cgi">Try again?</a>
347				// check for other errors & remove cclite link
348				if (preg_match('/^(.*?' . $username . '.*' . $registry . '[^<]*)/mi', $logon, $results)) {
349					$err_msg = trim($results[0]);
350					$logon = 'failed';	// error in $results[0]
351				} elseif (preg_match('/HTTP\/1.1 302/mis', $logon) && preg_match('/<BODY.*?>(.*)<\/BODY>/mis', $logon, $results)) {
352					$err_msg = trim(strip_tags($results[0], '<br />'));
353					//$logon = 'failed';
354				}
355			}
356
357			if ($logon && $logon != 'failed') {
358				preg_match_all('|Set-Cookie: (.*);|U', $logon, $results);
359				$cookies = implode("; ", $results[1]);
360				return [$logon, $cookies];
361			}
362		} else {
363			$err_msg = 'No result from cclite server.';
364		}
365
366		return ['failed', $err_msg];
367	}
368
369	// used to transport merchant key hash - probably duplicates of tiki fns REFACTOR?
370	static function urlsafe_b64encode($string)
371	{
372		$data = base64_encode($string);
373		$data = str_replace(['+', '/', '='], ['-', '_', ''], $data);
374		return $data;
375	}
376
377	static function urlsafe_b64decode($string)
378	{
379		$data = str_replace(['-', '_'], ['+', '/'], $string);
380		$mod4 = strlen($data) % 4;
381		if ($mod4) {
382			$data .= substr('====', $mod4);
383		}
384		return base64_decode($data);
385	}
386}
387
388global $cclitelib;
389$cclitelib = new CCLiteLib;
390