Эх сурвалжийг харах

Merge branch 'master' of /home/karma/dev/karmasolutions/karma_solutions/karmafw/repository

Max F 5 жил өмнө
parent
commit
0c35349272

+ 19 - 2
helpers/helpers_array.php

@@ -41,7 +41,7 @@ if (! function_exists('arrayAddKeyFromColumn')) {
 }
 
 if (! function_exists('arrayGroupByColumn')) {
-	function arrayGroupByColumn($array, $column_key) {
+	function arrayGroupByColumn($array, $column_key, $preserve_keys=true) {
 		$results = array();
 		foreach ($array as $k => $v) {
 			if (is_callable($column_key)) {
@@ -53,7 +53,12 @@ if (! function_exists('arrayGroupByColumn')) {
 			if (! isset($results[$key_value])) {
 				$results[$key_value] = array();
 			}
-			$results[$key_value][$k] = $v;
+			if ($preserve_keys) {
+				$results[$key_value][$k] = $v;
+				
+			} else {
+				$results[$key_value][] = $v;
+			}
 		}
 		return $results;
 	}
@@ -130,3 +135,15 @@ if (! function_exists('exportToCsvFile')) {
 		exit;
 	}
 }
+
+
+if (! function_exists('array_map_with_keys')) {
+	function array_map_with_keys($func, $array) {
+		$ret = [];
+		foreach ($array as $k => $v) {
+			$ret[$k] = $func($v, $k);
+		}
+		return $ret;
+	}
+}
+

+ 40 - 0
helpers/helpers_form.php

@@ -0,0 +1,40 @@
+<?php
+
+function checkEmail($email) {
+	// format: abc123@cde456.aa | abc123@cde456.aaa
+	return !! preg_match(" /^[^\W][a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)*\@[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)*\.[a-zA-Z]{2,4}$/ ", $email);
+}
+
+function checkPhone($phone) {
+	// format: 00 00 00 00 00,
+	return !! preg_match(" \^(\d\d\s){4}(\d\d)$\ ", $phone);
+}
+
+function checkUrl($url) {
+	// format http://www.example.com | www.example.com | http://subdomain.example.com | example.com
+	return !! preg_match(" \^(http|https|ftp):\/\/([\w]*)\.([\w]*)\.(com|net|org|biz|info|mobi|us|cc|bz|tv|ws|name|co|me)(\.[a-z]{1,3})?\z/i ", $url);
+}
+
+function checkLogin($login, $min_length=3, $max_length=16) {
+	// format: abc_123
+	return !! preg_match(" \^[a-zA-Z0-9_]{" . $min_length . "," . $max_length . "}$\ ", $login);
+}
+
+function checkDateFr($date) {
+	// format: 00/00/0000
+	return !! preg_match(" \^([0-3][0-9]})(/)([0-9]{2,2})(/)([0-3]{2,2})$\ ", $date);
+}
+
+function checkZipcode($zipcode) {
+	// format: 00000
+	return !! preg_match(" \^[0-9]{5,5}$\ ", $zipcode);
+}
+
+function checkIPv4($ip) {
+	return !! preg_match(" \^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:[.](?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$\ ", $ip);
+}
+
+function checkHexColor($color) {
+	return !! preg_match(" \^#(?:(?:[a-f\d]{3}){1,2})$/i ", $color);
+}
+

+ 2 - 2
helpers/helpers_smtp_hooks.php

@@ -4,7 +4,7 @@ use \KarmaFW\Lib\Email_lib;
 
 
 function smtp_hook_email($to, $subject, $message_html, $message_text='', $from=null, $from_name=null, $options=[]) {
-	// redirige les emails vers vers SMTP_HOOK_EMAIL
+	// redirige les emails sortants vers l'adresse SMTP_HOOK_EMAIL
 	if (! defined('SMTP_HOOK_EMAIL')) {
 		return false;
 	}
@@ -14,7 +14,7 @@ function smtp_hook_email($to, $subject, $message_html, $message_text='', $from=n
 }
 
 function smtp_hook_domain($to, $subject, $message_html, $message_text='', $from=null, $from_name=null, $options=[]) {
-	// redirige les emails vers vers SMTP_HOOK_DOMAIN  -  exemple:  paul.martin@gmail.com => paul.martin__gmail.com@mon-domaine-a-moi.com
+	// redirige les emails sortants vers le domaine SMTP_HOOK_DOMAIN  -  exemple:  paul.martin@gmail.com => paul.martin__gmail.com@mon-domaine-a-moi.com
 	if (! defined('SMTP_HOOK_DOMAIN')) {
 		return false;
 	}

+ 11 - 3
src/Database/Sql/SqlQuery.php

@@ -118,10 +118,18 @@ class SqlQuery
 
 		$this->status = 'running';
 		$ts_start = microtime(true);
+		
+
+		if (! empty($_GET['debug_sql'])) {
+			echo $query . "<hr />";
+		}
+
+		$_query = $query;
+		if (! empty($_GET['dry_sql'])) {
+			$_query = "select 1 from ( select 1 ) tmp where 0";
+		}
 
-		//echo $query . "<hr />";
-		$rs = $this->db->getDriver()->execute($query);
-		//pre($query);
+		$rs = $this->db->getDriver()->execute($_query);
 		//pre($rs);
 
 		$ts_end = microtime(true);

+ 77 - 3
src/Database/Sql/SqlTable.php

@@ -79,10 +79,47 @@ class SqlTable
 		// TODO: gerer on_duplicate_key_updates comme le where, dans un tableau et non dans un string
 
 		$query = "insert " . $ignore_sql . " into " . $this->table_name . " (" . $fields_sql . ") values " . $inserts_sql . " " . $on_duplicate_key_updates_sql;
+
+		if (! empty($options['debug'])) {
+			echo "<pre>" .preg_replace('/\s+/', ' ', $query) . "</pre>";
+		}
+
+		if (! empty($options['dry'])) {
+			return true;
+		}
+
 		return $this->db->createQuery()->executeInsertAll($query);
 	}
 
 
+	public function insertSelect($insert_table, $insert_keys=null, $where=null, $options=[]) 
+	{
+		$options_select = array_slice($options, 0);
+		unset($options_select['debug']);
+
+		$ignore_sql = empty($options['ignore']) ? '' : 'ignore';
+		
+		$on_duplicate_key_updates_sql = empty($options['on_duplicate_key_updates']) ? "" : ("on duplicate key update " . $options['on_duplicate_key_updates']);
+
+		$sql_keys = $insert_keys ? ("(" . $insert_keys . ")") : "";
+
+		$insert = "insert " . $ignore_sql . " into " . $insert_table . " " . $sql_keys . PHP_EOL;
+
+		$query = $this->buildQuery($where, $options_select);
+		$query = $insert . " " . $query . " " . $on_duplicate_key_updates_sql;
+
+		if (! empty($options['debug'])) {
+			echo "<pre>" .preg_replace('/\s+/', ' ', $query) . "</pre>";
+		}
+
+		if (! empty($options['dry'])) {
+			return true;
+		}
+
+		return $this->db->createQuery()->execute($query);
+	}
+
+
 	public function update(array $updates=[], array $where=[], $options=[]) /* : int */
 	{
 		$limit_sql = (isset($options['limit']) && ! is_null($options['limit'])) ? ("limit " . $options['limit']) : "";
@@ -101,6 +138,15 @@ class SqlTable
 					set " . $this->db->buildSqlUpdateValues($updates) . "
 					where " . $this->db->buildSqlWhere($where) . "
 					" . $limit_sql;
+
+		if (! empty($options['debug'])) {
+			echo "<pre>" .preg_replace('/\s+/', ' ', $query) . "</pre>";
+		}
+
+		if (! empty($options['dry'])) {
+			return true;
+		}
+
 		return $this->db->createQuery()->executeUpdate($query);
 	}
 
@@ -112,6 +158,15 @@ class SqlTable
 		$query = "delete from " . $this->table_name . "
 					where " . $this->db->buildSqlWhere($where) . "
 					" . $limit_sql;
+
+		if (! empty($options['debug'])) {
+			echo "<pre>" .preg_replace('/\s+/', ' ', $query) . "</pre>";
+		}
+
+		if (! empty($options['dry'])) {
+			return true;
+		}
+
 		return $this->db->createQuery()->executeDelete($query);
 	}
 
@@ -137,15 +192,28 @@ class SqlTable
 	public function getAll($where=null, $options=[]) /* : array */
 	{
 		$query = $this->buildQuery($where, $options);
+
+		if (! empty($options['dry'])) {
+			return [];
+		}
+
 		return $this->db->createQuery()->executeSelectAll($query);
 	}
 
 	public function getAllWithFoundRows($where=null, $options=[]) /* : array */
 	{
 		$query = $this->buildQuery($where, $options);
-		$rs = $this->db->createQuery()->execute($query);
-		$data = $rs->fetchAll();
-		$found_rows = $rs->getfoundRowsCount();
+
+		if (! empty($options['dry'])) {
+			$data = [];
+			$found_rows = 0;
+
+		} else {
+			$rs = $this->db->createQuery()->execute($query);
+			$data = $rs->fetchAll();
+			$found_rows = $rs->getfoundRowsCount();
+
+		}
 		return ['found_rows' => $found_rows, 'data' => $data];
 	}
 
@@ -167,7 +235,13 @@ class SqlTable
 	{
 		$options['limit'] = 1;
 		//return $this->getAll($where, $options)->fetchOne();
+
 		$query = $this->buildQuery($where, $options);
+
+		if (! empty($options['dry'])) {
+			return [];
+		}
+
 		return $this->db->createQuery()->executeSelectOne($query);
 	}
 

+ 7 - 0
src/Database/Sql/SqlTableModel.php

@@ -103,6 +103,13 @@ class SqlTableModel
 		return $db->getTable(static::$table_name)->insertAll($rows, $options);
 	}
 
+	public static function insertSelect($insert_table, $insert_keys=null, $rows=[], $options=[])
+	{
+		$db = static::getDb();
+		static::checkTable();
+		return $db->getTable(static::$table_name)->insertSelect($insert_table, $insert_keys, $rows, $options);
+	}
+
 	public static function update($updates=[], $where=[], $options=[])
 	{
 		$db = static::getDb();

+ 2 - 0
src/Database/Sql/SqlTools.php

@@ -115,6 +115,7 @@ class SqlTools
 	public function buildSqlWhere($where)
 	{
         $where_sql = array("1" => "1");
+        
         if (! empty($where)) {
             foreach ($where as $key => $value) {
                 if (is_null($value)) {
@@ -147,6 +148,7 @@ class SqlTools
                     $where_sql[] = (string) $value;
                     
                 }else{
+                    pre($where, 1);
                     $where_sql[] = $key . ' = ' . $this->escape($value);
                     //$where_sql[] = $key . ' = ' . (string) $value;
                 }

+ 44 - 22
src/Lib/Payment/Paypal_lib.php

@@ -14,7 +14,7 @@ class Paypal_lib
 	// https://github.com/paypal/PayPal-PHP-SDK/wiki/Making-First-Call
 	// https://www.grafikart.fr/tutoriels/paypal-express-checkout-rest-962
 	
-	public static function PaymentPaypal($payment_label='', $payment_product_label='', $payment_price=0, $customer_description='')
+	public static function paymentPaypal($url_callback="/paypal-callback", $url_cancel="/paypal-cancel", $payment_label='', $payment_price=0, $products_details=[], $customer_description='', $optionnal_data=[])
 	{
 		// Paypal STEP 1 (creating payment)
 
@@ -22,7 +22,7 @@ class Paypal_lib
 			return false;
 		}
 
-		if (PAYPAL_ENV == 'PROD') {
+		if (in_array(PAYPAL_ENV, ['PROD', 'LIVE'])) {
 			if (! defined('PAYPAL_PROD_CLIENT_ID') || ! defined('PAYPAL_PROD_SECRET')) {
 				return false;
 			}
@@ -51,7 +51,7 @@ class Paypal_lib
 			//'log.LogEnabled' => true,
 			//'log.FileName' => '/tmp/PayPal.log',
 			//'log.LogLevel' => 'FINE',
-			'mode' => (PAYPAL_ENV == 'PROD') ? 'live' : 'sandbox',
+			'mode' => (in_array(PAYPAL_ENV, ['PROD', 'LIVE'])) ? 'live' : 'sandbox',
 		]);
 
 		
@@ -74,15 +74,19 @@ class Paypal_lib
 			$transaction->setDescription($payment_label); // optionnal
 		}
 
-		if (! empty($payment_product_label)) {
+		if (! empty($products_details)) {
 			// specify products list
 			$list = new \PayPal\Api\ItemList();
-			$item = (new \PayPal\Api\Item())
-	                ->setName($payment_product_label)
-	                ->setPrice($payment_price)
-	                ->setCurrency('EUR')
-	                ->setQuantity(1);
-	        $list->addItem($item);
+			
+			foreach ($products_details as $product_detail) {
+				$item = (new \PayPal\Api\Item())
+		                ->setName($product_detail['name'])
+		                ->setPrice($product_detail['price'])
+		                ->setCurrency('EUR')
+		                ->setQuantity($product_detail['quantity']);
+		        $list->addItem($item);
+			}
+
 			$transaction->setItemList($list); // optionnal
 		}
 
@@ -95,7 +99,7 @@ class Paypal_lib
 		// set redirections urls
 		$scheme = ( (! empty($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] == 'https') || (! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (! empty($_SERVER['REDIRECT_HTTPS']) && $_SERVER['REDIRECT_HTTPS'] == 'on') ) ? 'https' : 'http';
 		$redirectUrls = new \PayPal\Api\RedirectUrls();
-		$redirectUrls->setReturnUrl($scheme."://" . $_SERVER['HTTP_HOST'] . "/paypal_callback")->setCancelUrl($scheme."://" . $_SERVER['HTTP_HOST'] . "/paypal_canceled");
+		$redirectUrls->setReturnUrl($scheme."://" . $_SERVER['HTTP_HOST'] . $url_callback)->setCancelUrl($scheme."://" . $_SERVER['HTTP_HOST'] . $url_cancel);
 
 
 		// Build payment
@@ -128,6 +132,13 @@ class Paypal_lib
 		}
 
 
+		$_SESSION['paypal_payment'][$paymentId] = [];
+
+        if (! empty($optionnal_data)) {
+        	$_SESSION['paypal_payment'][$paymentId] = array_merge($_SESSION['paypal_payment'][$paymentId], $optionnal_data);
+        }
+
+
 		return [
 			'approval_url' => $approval_url,
 			'paypal_error' => $paypal_error,
@@ -137,7 +148,7 @@ class Paypal_lib
 
 
 
-	public static function PaymentPaypal_Callback()
+	public static function paymentPaypal_Callback()
 	{
 		// Paypal STEP 2 (validating payment)
 
@@ -145,7 +156,7 @@ class Paypal_lib
 			return false;
 		}
 
-		if (PAYPAL_ENV == 'PROD') {
+		if (in_array(PAYPAL_ENV, ['PROD', 'LIVE'])) {
 			if (! defined('PAYPAL_PROD_CLIENT_ID') || ! defined('PAYPAL_PROD_SECRET')) {
 				return false;
 			}
@@ -198,20 +209,31 @@ class Paypal_lib
 			}
 		}
 
-		$paypal_result = [
-			'payment_ok' => $payment_ok,
-	    	'paymentId' => $paymentId,
-	    	'token' => $token,
-	    	'PayerID' => $PayerID,
-	    	'payment' => $payment,
-		];
+
+		$_SESSION['paypal_payment'][$paymentId]['payment_accepted'] = $payment_ok;
+		$_SESSION['paypal_payment'][$paymentId]['payment_id'] = $paymentId;
+		$_SESSION['paypal_payment'][$paymentId]['token'] = $token;
+		$_SESSION['paypal_payment'][$paymentId]['PayerID'] = $PayerID;
+		//$_SESSION['paypal_payment'][$paymentId]['payment'] = $payment;
 
 		if (! $payment_ok) {
-			return $paypal_result;
+			$_SESSION['paypal_payment'][$paymentId]['payment'] = $payment;
+
+			return $_SESSION['paypal_payment'][$paymentId];
 		}
 
 
 		if (false) {
+			// DEBUG
+
+			$paypal_result = [
+				'payment_ok' => $payment_ok,
+		    	'paymentId' => $paymentId,
+		    	'token' => $token,
+		    	'PayerID' => $PayerID,
+		    	'payment' => $payment,
+			];
+
 			$transaction = $paypal_result['payment']->getTransactions()[0];
 
 			echo "paymentId: " . $paymentId . "<hr />";
@@ -225,7 +247,7 @@ class Paypal_lib
 			echo "<pre>" . print_r($paypal_result['payment'], 1);
 		}
 
-		return $paypal_result;
+		return $_SESSION['paypal_payment'][$paymentId];
 
 	}
 

+ 163 - 0
src/Lib/Payment/Payplug_lib.php

@@ -0,0 +1,163 @@
+<?php
+
+namespace KarmaFW\Lib\Payment;
+
+//use \Payplug\Payplug;
+
+
+class Payplug_lib
+{
+	// infos API: https://docs.payplug.com/api/paymentpage.html
+
+	public static function getKeys($type=null)
+	{
+		if (in_array(PAYPLUG_ENV, ['LIVE', 'PROD'])) {
+			if (! defined('PAYPLUG_LIVE_PUBLIC_KEY') || ! defined('PAYPLUG_LIVE_SECRET_KEY')) {
+				return false;
+			}
+			$payplug_public_key = PAYPLUG_LIVE_PUBLIC_KEY;
+			$payplug_secret_key = PAYPLUG_LIVE_SECRET_KEY;
+
+		} else {
+			if (! defined('PAYPLUG_TEST_PUBLIC_KEY') || ! defined('PAYPLUG_TEST_SECRET_KEY')) {
+				return false;
+			}
+			$payplug_public_key = PAYPLUG_TEST_PUBLIC_KEY;
+			$payplug_secret_key = PAYPLUG_TEST_SECRET_KEY;
+		}
+		if ($type == 'public') 	{
+			return $payplug_public_key;
+		}
+		if ($type == 'secret') 	{
+			return $payplug_secret_key;
+		}
+		return [
+			'public' => $payplug_public_key,
+			'secret' => $payplug_secret_key,
+		];
+	}
+
+
+	public static function getCustomerData($client, $shipping_address, $delivery_type=null)
+	{
+		$payment_data = [
+			'title'               => ($client['gender'] == 'female') ? 'mrs' : 'mr',
+			'first_name'          => $client['firstname'],
+			'last_name'           => $client['firstname'],
+			'mobile_phone_number' => $client['phone'],
+			'email'               => $client['email'],
+			'address1'            => $shipping_address['address'],
+			'postcode'            => $shipping_address['zipcode'],
+			'city'                => $shipping_address['city'],
+			'country'             => 'FR',
+			'language'            => 'fr'
+		];
+
+		if ($delivery_type) {
+			$payment_data['delivery_type'] = $delivery_type;
+		}
+
+		return $payment_data;
+	}
+
+
+	/* API Hosted page */
+
+	public static function paymentPayplugHosted($client, $shipping_address, $total_price_ttc, $order_id=null)
+	{
+
+		if (substr($client['phone'], 0, 1) == '0') {
+			$client['phone'] = '+33' . substr($client['phone'], 1);
+		}
+		//pre($client, 1);
+
+		$payplug_secret_key = self::getKeys('secret');
+		\Payplug\Payplug::setSecretKey($payplug_secret_key);
+
+		$payment_data = [
+			'amount'           => $total_price_ttc * 100,
+			'currency'         => 'EUR',
+			'save_card'        => false,
+			'billing'          => self::getCustomerData($client, $shipping_address),
+			'shipping'        => self::getCustomerData($client, $shipping_address, 'BILLING'),
+			'hosted_payment' => [
+				'return_url'   => 'http://' . $_SERVER['SERVER_NAME'] . '/payment/payplug/payment-ok?id=' . $order_id,
+				'cancel_url'   => 'http://' . $_SERVER['SERVER_NAME'] . '/payment/payplug/payment-cancel?id=' . $order_id,
+			],
+			'notification_url' => 'http://' . $_SERVER['SERVER_NAME'] . '/payment/payplug/payment-notification?id=' . $order_id,
+			'metadata'      => [
+				'order_id' => $order_id,
+			]
+		];
+		//pre($payment_data, 1);
+
+		$payment = \Payplug\Payment::create($payment_data);
+
+		$payment_url = $payment->hosted_payment->payment_url;
+		$payment_id = $payment->id;
+
+		return [
+			'payment_url' => $payment_url,
+			'payment_id' => $payment_id,
+		];
+	}
+
+
+
+	/* API Lightbox */
+	public static function paymentPayplugLightbox($client, $shipping_address, $total_price_ttc, $order_id=null)
+	{
+
+		if (substr($client['phone'], 0, 1) == '0') {
+			$client['phone'] = '+33' . substr($client['phone'], 1);
+		}
+		//pre($client, 1);
+
+		$payplug_secret_key = self::getKeys('secret');
+		\Payplug\Payplug::setSecretKey($payplug_secret_key);
+
+
+		$payment = \Payplug\Payment::create(
+			[
+			    'amount'         => $total_price_ttc * 100,
+			    'currency'       => 'EUR',
+			    'billing'        => self::getCustomerData($client, $shipping_address),
+			    'shipping'       => self::getCustomerData($client, $shipping_address, 'BILLING'),
+			    'hosted_payment' => array(
+			        'return_url' => 'http://' . $_SERVER['SERVER_NAME'] . '/payment/payplug/payment-ok?id=' . $order_id,
+			        'cancel_url' => 'http://' . $_SERVER['SERVER_NAME'] . '/payment/payplug/payment-cancel?id=' . $order_id,
+			    ),
+			    'notification_url' => 'http://' . $_SERVER['SERVER_NAME'] . '/payment/payplug/payment-notification?id=' . $order_id,
+			]
+		);
+
+		return $payment;
+	}
+
+
+	/* API payplug.js */
+	public static function paymentPayplugPayplugJs($token, $client, $shipping_address, $total_price_ttc, $order_id=null)
+	{
+		if (substr($client['phone'], 0, 1) == '0') {
+			$client['phone'] = '+33' . substr($client['phone'], 1);
+		}
+
+		$payplug_secret_key = self::getKeys('secret');
+		\Payplug\Payplug::setSecretKey($payplug_secret_key);
+
+		$payment = \Payplug\Payment::create(array(
+			'amount'         => $total_price_ttc * 100,
+			'currency'       => 'EUR',
+			'payment_method' => $token,
+			'billing'        => self::getCustomerData($client, $shipping_address),
+			'shipping'       => self::getCustomerData($client, $shipping_address, 'BILLING'),
+			'notification_url' => 'http://karma:dev@' . $_SERVER['SERVER_NAME'] . '/payment/payplug/payment-notification?id=' . $order_id,
+		));
+
+		return $payment;
+	}
+
+
+
+}
+

+ 194 - 9
src/Lib/Payment/Stripe_lib.php

@@ -8,15 +8,8 @@ use \Stripe\Stripe;
 class Stripe_lib
 {
 
-	public static function PaymentStripe($stripe_token, $customer_id=null, $payment_label='', $payment_price=0, $save_customer=false, $new_customer_email=null, $new_customer_description=null)
+	public static function getKeys($type=null)
 	{
-		if (empty($payment_price) || ! defined('STRIPE_ENV')) {
-			return false;
-		}
-
-		// TEST CB valide  => visa: 4242 4242 4242 4242  /  mastercard: 5555 5555 5555 4444
-		// TEST CB refusée => visa: 4242 4242 4242 1214  /  mastercard: 5555 5555 5555 7777
-
 		if (STRIPE_ENV == 'LIVE') {
 			if (! defined('STRIPE_LIVE_PUBLIC_KEY') || ! defined('STRIPE_LIVE_SECRET_KEY')) {
 				return false;
@@ -30,10 +23,114 @@ class Stripe_lib
 			}
 			$stripe_public_key = STRIPE_TEST_PUBLIC_KEY;
 			$stripe_secret_key = STRIPE_TEST_SECRET_KEY;
-		}	
+		}
+		if ($type == 'public') 	{
+			return $stripe_public_key;
+		}
+		if ($type == 'secret') 	{
+			return $stripe_secret_key;
+		}
+		return [
+			'public' => $stripe_public_key,
+			'secret' => $stripe_secret_key,
+		];
+
+	}
+
+
+	public static function paymentChargeByCard($card, $customer_id=null, $payment_label='', $payment_price=0, $save_customer=false, $new_customer_email=null, $new_customer_description=null)
+	{
+		/*
+		$card = [
+			'number' => '4242424242424242',
+			'exp_month' => 12,
+			'exp_year' => 2020,
+			'cvc' => '314',
+		];
+		*/
+
+		$stripe_secret_key = self::getKeys('secret');
+		\Stripe\Stripe::setApiKey($stripe_secret_key);
+
+
+		// Warning: l'API \Stripe\Token requiert que l'on ai vérifié son téléphone sur notre compte Stripe
+
+		$client_secret = \Stripe\Token::create([
+		  'card' => $card,
+		]);
+
+		$payment_result = \KarmaFW\Lib\Payment\Stripe_lib::paymentCharge($client_secret, $customer_id, $payment_label, $payment_price, $save_customer, $new_customer_email, $new_customer_description);
+
+
+		/*
+		$result = Array
+		(
+		    [payment_accepted] => 1
+		    [customer_id] => 
+		    [stripe_token] => Stripe\Token Object
+		        (
+		            [id] => tok_1Fry1qBSCxevOCvKzN3APSvV
+		            [object] => token
+		            [card] => Stripe\Card Object
+		                (
+		                    [id] => card_1Fry1pBSCxevOCvKCxJk8v5u
+		                    [object] => card
+		                    [address_city] => 
+		                    [address_country] => 
+		                    [address_line1] => 
+		                    [address_line1_check] => 
+		                    [address_line2] => 
+		                    [address_state] => 
+		                    [address_zip] => 
+		                    [address_zip_check] => 
+		                    [brand] => Visa
+		                    [country] => US
+		                    [cvc_check] => unchecked
+		                    [dynamic_last4] => 
+		                    [exp_month] => 12
+		                    [exp_year] => 2020
+		                    [fingerprint] => hZOhEfHv06Xhjqll
+		                    [funding] => credit
+		                    [last4] => 4242
+		                    [metadata] => Stripe\StripeObject Object
+		                        (
+		                        )
+
+		                    [name] => 
+		                    [tokenization_method] => 
+		                )
+
+		            [client_ip] => 82.64.213.186
+		            [created] => 1576896558
+		            [livemode] => 
+		            [type] => card
+		            [used] => 
+		        )
+
+		    [stripe_error] => 
+		)
+		*/
+
+		return $payment_result;
+	}
+
+
+
+	public static function paymentCharge($stripe_token, $customer_id=null, $payment_label='', $payment_price=0, $save_customer=false, $new_customer_email=null, $new_customer_description=null)
+	{
+		// Note: $stripe_token peut venir soit d'un \Stripe\Token (voir methode paymentChargeByCard), soit depuis Stripe.js
+
+		if (empty($payment_price) || ! defined('STRIPE_ENV')) {
+			return false;
+		}
+
+		// TEST CB valide  => visa: 4242 4242 4242 4242  /  mastercard: 5555 5555 5555 4444
+		// TEST CB refusée => visa: 4242 4242 4242 1214  /  mastercard: 5555 5555 5555 7777
 
 		$stripe_error = '';
 		$payment_ok = false;
+
+		$stripe_secret_key = self::getKeys('secret');
 		\Stripe\Stripe::setApiKey($stripe_secret_key);
 
 		try {
@@ -92,13 +189,101 @@ class Stripe_lib
 			$payment_ok = true;
 		}
 
+		$payment_id = $charge->id;
+		$amount = $charge->amount;
+
 		return [
 			'payment_accepted' => $payment_ok,
+			'payment_id' => $payment_id,
+			'amount' => $amount,
 			'customer_id' => $customer_id,
 			'stripe_token' => $stripe_token,
 			'stripe_error' => $stripe_error,
 		];
 	}
 
+
+
+	// API Methode Stripe.js ajax
+
+	public function paymentIntentInit($unit, $quantity=1, $optionnal_data=[])
+	{
+		// Paiement en 2 temps, via Stripe.js - 1/2  (method 1)
+		// Methode appelée en ajax
+
+		$stripe_secret_key = self::getKeys('secret');
+        \Stripe\Stripe::setApiKey($stripe_secret_key);
+
+        $currency = 'eur';
+
+        $intent = \Stripe\PaymentIntent::create([
+            'amount' => $quantity * $unit * 100,
+            'currency' => $currency,
+        ]);
+
+        $client_secret = $intent->client_secret;
+
+
+		if (empty($_SESSION['stripe_payment'])) {
+			$_SESSION['stripe_payment'] = [];
+		}
+
+        $_SESSION['stripe_payment'][$client_secret] = [
+        	'unit' => $unit,
+        	'quantity' => $quantity,
+        	'currency' => $currency,
+        ];
+
+        if (! empty($optionnal_data)) {
+        	$_SESSION['stripe_payment'][$client_secret] = array_merge($_SESSION['stripe_payment'][$client_secret], $optionnal_data);
+        }
+
+        //echo $client_secret; // renvoie le token au javascript de Stripe.js
+
+        return $client_secret;
+	}
+
+
+	public function paymentIntentConfirm()
+	{
+		// Paiement en 2 temps, via Stripe.js - 2/2  (method 1)
+		// Methode appelée en ajax
+
+        $payment_id = get('pid');
+        $client_secret = get('sec');
+
+        if (empty($client_secret)) {
+            showError403('Paiment refusé. (Cause = error 1)');
+        }
+
+        if (empty($_SESSION['stripe_payment'][$client_secret])) {
+            showError403('Paiment refusé. (Cause = error 2)');
+        }
+
+        $payment = $_SESSION['stripe_payment'][$client_secret];
+        if (empty($payment)) {
+            showError403('Paiment refusé. (Cause = error 3)');
+        }
+
+        $quantity = $payment['quantity'];
+        $unit = $payment['unit'];
+        $currency = $payment['currency'];
+
+        if (empty($quantity)) {
+            showError403('Paiment refusé. (Cause = error 4)');
+        }
+
+        if (empty($unit)) {
+            showError403('Paiment refusé. (Cause = error 5)');
+        }
+
+
+        $_SESSION['stripe_payment'][$client_secret]['payment_id'] = $payment_id;
+
+        //echo "ok"; // permet de dire au javascript de rediriger ensuite vers la page de confirmation de commande
+
+        return $_SESSION['stripe_payment'][$client_secret];
+	}
+
 }
 

+ 60 - 7
src/Routing/Route.php

@@ -11,6 +11,9 @@ class Route
 	private $match_type = 'exact';
 	private $regex_params = [];
 	private $callback = null;
+	private $prefix = '';
+	private $prefix_callback = null;
+	private $nomatch_patterns = [];
 
 
 	public function __construct()
@@ -35,7 +38,7 @@ class Route
 	// Get route match url
 	public function getMatchUrl()
 	{
-		return $this->match_url;
+		return $this->prefix . $this->match_url;
 	}
 
 	// Set route match type (exact, startsWith, endsWith, regex, regexStartsWith, regexEndsWith)
@@ -75,44 +78,94 @@ class Route
 	}
 
 
+	// Set route prefix
+	public function setPrefix($prefix=null, $callback=null)
+	{
+		$this->prefix = $prefix;
+		$this->prefix_callback = $callback;
+	}
+
+	// Get route prefix
+	public function getPrefix()
+	{
+		return $this->prefix;
+	}
+
+	// Set route prefix callback
+	public function setPrefixCallback($callback)
+	{
+		$this->prefix_callback = $callback;
+	}
+
+	// Get route prefix callback
+	public function getPrefixCallback()
+	{
+		return $this->prefix_callback;
+	}
+
+
+	// Declare pattern to not match
+	public function notMatch($pattern)
+	{
+		if (! is_array($pattern)) {
+			$pattern = [$pattern];
+		}
+		foreach ($pattern as $p) {
+			$this->nomatch_patterns[] = $p;
+		}
+	}
+
+
+
 	// Check if route is matching the request_method and request_uri
 	public function match($request_method, $request_uri)
 	{
 		if (empty($this->methods) || in_array($request_method, $this->methods)) {
 
 			$request_uri_short = explode('?', $request_uri)[0];
+
+			// on verifie qu'il n'y a pas un pattern de nomatching
+			if ($this->nomatch_patterns) {
+				foreach ($this->nomatch_patterns as $pattern) {
+					if (preg_match('#' . $pattern . '#', $request_uri_short, $regs)) {
+						return null;
+					}
+				}
+			}
+
+			$match_url = $this->getMatchUrl();
 			
 			// exact match
 			if ($this->match_type == 'exact') {
-				if ($request_uri_short === $this->match_url) {
+				if ($request_uri_short === $match_url) {
 					return [];
 				}
 			}
 
 			// startsWith
 			if ($this->match_type == 'startsWith') {
-				if (substr($request_uri_short, 0, strlen($this->match_url)) === $this->match_url) {
+				if (substr($request_uri_short, 0, strlen($match_url)) === $match_url) {
 					return [];
 				}
 			}
 
 			// endsWith
 			if ($this->match_type == 'endsWith') {
-				if (substr($request_uri_short, -1 * strlen($this->match_url)) === $this->match_url) {
+				if (substr($request_uri_short, -1 * strlen($match_url)) === $match_url) {
 					return [];
 				}
 			}
 
 			// regex / regexStartsWith / regexEndsWith
 			if (in_array($this->match_type, ['regex', 'regexStartsWith', 'regexEndsWith'])) {
-				$match_pattern = '#^' . $this->match_url . '$#';
+				$match_pattern = '#^' . $match_url . '$#';
 	
 				if ($this->match_type == 'regexStartsWith') {
-					$match_pattern = '#^' . $this->match_url . '#';
+					$match_pattern = '#^' . $match_url . '#';
 				}
 
 				if ($this->match_type == 'regexEndsWith') {
-					$match_pattern = '#' . $this->match_url . '$#';
+					$match_pattern = '#' . $match_url . '$#';
 				}
 
 				if (preg_match($match_pattern, $request_uri_short, $regs)) {

+ 72 - 15
src/Routing/Router.php

@@ -6,28 +6,41 @@ namespace KarmaFW\Routing;
 class Router
 {
 	private static $routes = [];
+	private static $prefixes = [];
+	private static $routed_url = null;
 
 
 	// Register a route in the router
 	public static function add($methods, $url_match, $callback=null, $type_match='exact', $regex_params=[])
 	{
-		$route = new Route();
-
-		$route->setMatchUrl($url_match);
-		$route->setCallback($callback);
-		$route->setMatchType($type_match	);
-		$route->setRegexParams($regex_params);
-		
-		if (! is_array($methods)) {
-			$methods = [$methods];
-		}
-		foreach ($methods as $method) {
-			$route->setMethod($method);
+		$prefixes = self::$prefixes ?: ['' => null];
+
+		$routes_group = new RoutesGroup;
+
+		foreach ($prefixes as $prefix => $prefix_callback) {
+			$route = new Route();
+
+			$route->setPrefix($prefix, $prefix_callback);
+			//$route->setPrefixCallback($prefix_callback);
+			$route->setMatchUrl($url_match);
+			$route->setCallback($callback);
+			$route->setMatchType($type_match	);
+			$route->setRegexParams($regex_params);
+			
+			if (! is_array($methods)) {
+				$methods = [$methods];
+			}
+			foreach ($methods as $method) {
+				$route->setMethod($method);
+			}
+
+			$routes_group->add($route);
+			self::$routes[] = $route;
 		}
 
-		self::$routes[] = $route;
 
-		return $route;
+		return $routes_group;
+		//return $route;
 	}
 
 
@@ -64,6 +77,11 @@ class Router
 				if ($debug) {
 					echo " => MATCH !<br />" . PHP_EOL;
 				}
+				
+				$prefix_callback = $route->getPrefixCallback();
+				if (! empty($prefix_callback) && is_callable($prefix_callback)) {
+					$prefix_callback();
+				}
 
 				$callback = $route->getCallback();
 				if (empty($callback)) {
@@ -71,6 +89,7 @@ class Router
 					return 0;
 
 				} else if (is_callable($callback)) {
+					self::$routed_url = $route;
 					self::routeRun($route, $callback, $request_method, $request_uri, $match_params);
 
 				} else {
@@ -131,8 +150,20 @@ class Router
 			return null;
 		}
 
+
+		$prefix = self::$routed_url ? self::$routed_url->getPrefix() : '';
+		if ($prefix) {
+			$route_prefix = $route->getPrefix();
+			$route->setPrefix($prefix);
+		}
+
 		$link = $route->getMatchUrl();
-		//echo "<pre>"; var_dump($route); exit;
+
+		if ($prefix) {
+			$route->setPrefix($route_prefix);
+		}
+
+
 		$link = rtrim($link, '$');
 		$link = ltrim($link, '^');
 
@@ -158,4 +189,30 @@ class Router
 		return $link;
 	}
 
+
+	public static function printRoutes()
+	{
+		dump(self::$prefixes);
+		dump(self::$routes);
+		exit;
+	}
+
+
+	public static function prefix($prefix, $callback)
+	{
+		self::$prefixes[$prefix] = $callback;
+	}
+
+
+	public static function clearPrefixes()
+	{
+		self::$prefixes = [];
+	}
+
+
+	public static function getRoutedUrl()
+	{
+		return self::$routed_url;
+	}
+
 }

+ 39 - 0
src/Routing/RoutesGroup.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace KarmaFW\Routing;
+
+
+class RoutesGroup
+{
+	protected $routes = [];
+
+	public function add($route)
+	{
+		$this->routes[] = $route;
+	}
+
+	public function setName($name)
+	{
+		foreach ($this->routes as $route) {
+			$route->setName($name);
+		}
+		return $this;
+	}
+	
+	public function setPrefix($prefix=null, $prefix_callback=null)
+	{
+		foreach ($this->routes as $route) {
+			$route->setPrefix($prefix, $prefix_callback);
+		}
+		return $this;
+	}
+
+	public function notMatch($pattern)
+	{
+		foreach ($this->routes as $route) {
+			$route->notMatch($pattern);
+		}
+		return $this;
+	}
+
+}

+ 42 - 20
src/Templates/PhpTemplate.php

@@ -41,47 +41,54 @@ class PhpTemplate
 		// PLUGINS
 
 		$template = $this;
-		$this->addPlugin('layout', function ($param) use ($template) {
+		$this->addPlugin('layout', function ($key) use ($template) {
 			// {layout my_layout_template.tpl.php}
-			$template->layout = $param;
+			$template->layout = $key;
 			return '';
 		});
-		$this->addPlugin('\/\/', function ($param) {
+		$this->addPlugin('\/\/', function ($key) {
 			// {// this is a comment}
 			return '';
 		});
-		$this->addPlugin('#', function ($param) {
+		$this->addPlugin('#', function ($key) {
 			// {# this is a comment}
 			return '';
 		});
-		$this->addPlugin('tr', function ($param) {
+		$this->addPlugin('assign', function ($key, $value) use ($template) {
+			// {assign var_name content of my variable}
+			$template->assign($key, $value);
+			return '';
+		});
+		$this->addPlugin('tr', function ($key) {
 			// {tr my text in english} ==> mon texte en francais
-			return gettext($param);
+			return gettext($key);
 		});
-		$this->addPlugin('include', function ($param) use ($template) {
+		$this->addPlugin('include', function ($key) use ($template) {
 			// {include my_template.tpl.php}
 			$template = new PhpTemplate($template->templates_dirs, $template->variables, null, $template->templates_dirs);
-			$templatechild_content = $template->fetch($param);
+			$templatechild_content = $template->fetch($key);
 			return $templatechild_content;
 		});
-		$this->addPlugin('routeUrl', function ($param) {
+		$this->addPlugin('routeUrl', function ($key, $value=[]) {
 			// {routeUrl login_page} ===> /login
-			$params = explode(' ', $param);
-			$route_name = array_shift($params);
-			$url_args = $params;
+			$route_name = $key;
+			$url_args = explode(' ', $value);
 			$url = getRouteUrl($route_name, $url_args);
 			return $url;
 		});
 
-		$this->addPlugin('block', function ($param, $matched_expr, $begin_block_offset_start, &$content) use ($template) {
+		$this->addPlugin('block', function ($key, $matched_expr, $begin_block_offset_start, &$content) use ($template) {
 			// {block block_name}my html content{/block}  ==> assign variable $block_name with block content
 			$begin_block_offset_end = $begin_block_offset_start + strlen($matched_expr);
 
 			$end_block_offset_start = strpos($content, '{/block}', $begin_block_offset_end);
 
 			if ($end_block_offset_start) {
-				$block = substr($content, $begin_block_offset_end, $end_block_offset_start - $begin_block_offset_end);
-				$template->assign($param, $block);
+				$block = isset($template->variables[$key]) ? $template->variables[$key] : '';
+
+				$block = substr($content, $begin_block_offset_end, $end_block_offset_start - $begin_block_offset_end) . $block;
+
+				$template->assign($key, $block);
 
 				$end_block_offset_end = $end_block_offset_start + strlen("{/block}");
 				$content = substr($content, 0, $begin_block_offset_start) . substr($content, $end_block_offset_end);
@@ -135,7 +142,7 @@ class PhpTemplate
 		}
 
 		if (is_null($tpl_path)) {
-			throw new \Exception("Template not found : " . $tpl, 1);
+			throw new \Exception("Template not found : " . $tpl . " (dirs: " . implode(" | ", $tpl_dirs) . ")", 1);
 		}
 		
 		//$tpl_vars = array_merge($this->variables, $extra_vars);
@@ -162,9 +169,10 @@ class PhpTemplate
 				foreach ($this->plugins as $prefix => $callback) {
 
 					if ($prefix != 'block') {
-						preg_match_all('/{' . $prefix . ' ([^}]+)}/', $content, $regs, PREG_SET_ORDER);
+						preg_match_all('/{' . $prefix . ' ([^} ]+)( ([^}]+))?}/', $content, $regs, PREG_SET_ORDER);
 						foreach($regs as $reg) {
-							$replaced = $callback($reg[1], $reg);
+							$value = isset($reg[3]) ? $reg[3] : null;
+							$replaced = $callback($reg[1], $value);
 							$content = str_replace($reg[0], $replaced, $content);
 						}
 					} else {
@@ -187,14 +195,28 @@ class PhpTemplate
 
 			// variables. ex: {$user_name} ==> John
 			if (true) {
-				preg_match_all('/{\$([a-zA-Z0-9_\[\]\']+)}/', $content, $regs, PREG_SET_ORDER);
+				preg_match_all('/{\$([a-zA-Z0-9._\[\]\']+)}/', $content, $regs, PREG_SET_ORDER);
 				foreach($regs as $reg) {
 					$var = $reg[1];
+					$var_parts = explode(".", $var);
 					
-					if (isset(${$var})) {
+					if (count($var_parts) > 1) {
+						// $variable.key  ==> $variable['key']
+
+						$var = ${ array_shift($var_parts) };
+						foreach ($var_parts as $part) {
+							$var = $var[ $part ];
+						}
+
+						$replaced = $var;
+						$content = str_replace($reg[0], $replaced, $content);
+
+					} else if (isset(${$var})) {
+						// $variable
 						$replaced = ${$var};
 						$content = str_replace($reg[0], $replaced, $content);
 					} else {
+
 						// if variable not exists, replace with empty string
 						$content = str_replace($reg[0], '', $content);
 					}