Bläddra i källkod

OAuth and Twig extensions that interact with Square API

Richard Knight 6 år sedan
förälder
incheckning
105a44229e

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/vendor

+ 2 - 1
composer.json

@@ -6,7 +6,8 @@
 		"test"
     ],
     "require": {
-        "bolt/bolt": "^3.6"
+        "bolt/bolt": "^3.6",
+        "square/connect": "^2.20180918"
     },
     "require-dev": {
         "phpunit/phpunit": "^4.7"

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1254 - 0
composer.lock


+ 13 - 0
src/Api/ApiClient.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Bolt\Extension\Ginger\Squarepay\Api;
+
+use Silex\Application;
+
+class ApiClient
+{
+	function __construct(Application $app)
+	{
+	}
+}
+

+ 100 - 0
src/Controller/SquareBackendController.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace Bolt\Extension\Ginger\Squarepay\Controller;
+
+use Bolt\Controller\Base;
+use Silex\Application;
+use Silex\ControllerCollection;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * The controller for Square backend routes.
+ * 
+ * Get an OAuth Access Token, and store it - it's valid for 30 days,
+ * then enters 15 day grace period during which it should usually be renewed
+ *
+ * @author Richard Knight <rich@apewave.com>
+ */
+class SquareBackendController extends Base
+{
+    public function __construct($config)
+    {
+        $this->config = $config;
+
+        // Create and configure a new API client object
+        $apiConfig = new \SquareConnect\Configuration();
+        $apiConfig->setAccessToken($config['sq_sandbox_token']);
+        $this->apiClient = new \SquareConnect\ApiClient($apiConfig);
+    }    
+    
+    public function addRoutes(ControllerCollection $collection)
+    {
+        $collection->match('/', [$this, 'squareDashboard']);
+        $collection->match('/request-oauth-token', [$this, 'oauthRequestToken']);
+        return $collection;
+    }
+
+    public function squareDashboard()
+    {
+        $params = [
+            'client_id' => $this->config['sq_app_id'],
+            'scope' => 'ITEMS_READ MERCHANT_PROFILE_READ PAYMENTS_READ PAYMENTS_WRITE ORDERS_READ',
+        ];
+        $uri = 'https://'.$this->config['sq_domain'].$this->config['sq_auth_url'];
+        $query = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
+        $auth_link = $uri.'?'.$query;
+        $context = [
+            'auth_link' => $auth_link, 
+            'config' => $this->config
+        ];
+        return $this->render('@Squareback/dashboard.twig', $context);
+    }
+
+    public function oauthRequestToken(Application $app, Request $request)
+    {
+        try {
+            $authorizationCode = $request->query->get('code');
+            if (!$authorizationCode) {
+                error_log('Authorization failed!');
+                throw new \Exception('Error Processing Request: Authorization failed!', 1);
+            }
+            $oauthToken = $this->getOAuthToken($authorizationCode);
+        } catch (Exception $e) {
+            error_log($e->getMessage());
+            return $this->render('@Squareback/error.twig', ['error' => $e->getMessage()]);
+        }
+        
+        // Now store the token
+        $manager = $app['filesystem'];
+        if ($manager->hasFilesystem('extensions_config')) {
+            $filesystem = $manager->getFilesystem('extensions_config');
+            $filesystem->put('.sqoatoken', $oauthToken);
+        } else {
+            return $this->render('@Squareback/error.twig', ['error' =>'Could not write .squotoken to app/config/extensions/']);
+        }
+        return $this->render('@Squareback/authorised.twig', ['token' => $oauthToken]);
+    }
+
+    // ------------------------------------------------------------------------------------------------------------------------------------------
+
+    private function getOAuthToken($authorizationCode) {
+        // Exchange the authorization code for an OAuth token
+        // Create an OAuth API client
+        $oauthApi = new \SquareConnect\Api\OAuthApi($this->apiClient);
+        $body = new \SquareConnect\Model\ObtainTokenRequest();
+      
+        // Set the POST body
+        $body->setClientId($this->config['sq_app_id']);
+        $body->setClientSecret($this->config['sq_app_secret']);
+        $body->setCode($authorizationCode);
+        try {
+            $result = $oauthApi->obtainToken($body);
+        } catch (Exception $e) {
+            error_log('Exception when calling OAuthApi->obtainToken: ' . $e->getMessage());
+            throw new Exception('Error Processing Request: Token exchange failed!', 1);
+        }
+        return $result->getAccessToken();
+    }
+
+}

+ 64 - 32
src/SquarepayExtension.php

@@ -2,10 +2,14 @@
 
 namespace Bolt\Extension\Ginger\Squarepay;
 
+use Bolt\Extension\Ginger\Squarepay\Api;
+use Bolt\Extension\Ginger\Squarepay\Twig;
 use Bolt\Extension\SimpleExtension;
 use Bolt\Menu\MenuEntry;
 use Silex\Application;
+
 use Silex\ControllerCollection;
+
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 
@@ -16,61 +20,89 @@ use Symfony\Component\HttpFoundation\Response;
  */
 class SquarepayExtension extends SimpleExtension
 {
-    protected function registerAssets() {
-		// $config = $this->getConfig();
+	private $apiClient = null;
+	
+	protected function registerServices(Application $app)
+	{
+        $app['square.apiClient'] = $app->share(function($app) {
+			
+			// Create and configure a new Square API client using the OAuth token
+			$manager = $app['filesystem'];
+			if (!$manager->hasFilesystem('extensions_config')) {
+				throw new Exception('Could not find filesystem extensions_config');
+			}
+			$filesystem = $manager->getFilesystem('extensions_config');
+			$path = '.sqoatoken';
+			$file = $filesystem->getFile($path);
+			if (!$file->exists($path)) {
+				throw new Exception('Could not find '.$path);
+			}
+			$oauthToken = $file->read($path);
+
+			$apiConfig = new \SquareConnect\Configuration();
+			$apiConfig->setAccessToken($oauthToken);
+			$apiClient = new \SquareConnect\ApiClient($apiConfig);
+			return new \SquareConnect\ApiClient($apiConfig);
+		});
+
+		$app['twig.runtime.square'] = $app->share(function ($app) {
+			return new Twig\SquareTwigRuntime($app['square.apiClient']);
+		});
+
+		$app['twig.runtimes'] = $app->extend(
+			'twig.runtimes',
+			function (array $runtimes) {
+                // You must append your array to the passed in $runtimes array and return it
+                return $runtimes + [
+                    Twig\SquareTwigRuntime::class => 'twig.runtime.square',
+                ];
+			}
+		);
 	}
 
 	protected function registerFrontendRoutes(ControllerCollection $collection)
 	{
-        $collection->match('/checkout', [$this, 'checkoutPaymentForm']);
+		$collection->match('/process-square', [$this, 'processSquarePaymentResponse']);
 	}
 	
-	protected function registerBackendRoutes(ControllerCollection $collection)
-	{
-        $collection->match('/extensions/square-authorisation', [$this, 'squareAuthorisation']);
-        $collection->match('/extensions/square-authorisation-result', [$this, 'squareAuthorisationHandleResult']);
-	}
-
 	protected function registerBackendControllers()
 	{
 		return [
-			'/extensions/square' => new Controller\SquareController()
+			'/extensions/square' => new Controller\SquareBackendController($this->getConfig())
 		];
 	}
 
 	protected function registerMenuEntries()
 	{
-        $menu = MenuEntry::create('square-menu', 'square')
-            ->setLabel('Square Payments')
-            ->setIcon('fa:credit-card-alt')
-            ->setPermission('settings')
-			// ->setRoute('square')
+		$menu = MenuEntry::create('square-menu', 'square')
+			->setLabel('Square')
+			->setIcon('fa:credit-card')
+			->setPermission('settings')
+			// ->setRoute('SquarepayExtension')
 			;
+		return [$menu];
+	}
 
-        return [
-            $menu
+	protected function registerTwigPaths()
+    {
+		// Register Twig namespace to use in controllers
+		return [
+            'templates/backend'  => ['namespace' => 'Squareback'],
+            'templates/frontend' => ['namespace' => 'Squarefront']
         ];
-	}
+    }
 	
 	protected function registerTwigFunctions() {
+        $getCatalog = [Twig\SquareTwigRuntime::class, 'getCatalog'];
+        $displayCCForm = [Twig\SquareTwigRuntime::class, 'displayCCForm'];
 		return [
-			'waggawoo' => ['waggawooFunction', ['is_safe' => ['html']]]
+			'getcatalog' => [ $getCatalog ],
+			'ccform' => [ $displayCCForm, ['is_safe' => ['html']] ]
 		];
 	}
 
-	public function waggawooFunction() {
-		return '<h1>WAGGA WAGGA WOO!</h1>';
+	public function processSquarePaymentResponse() {
+		// TODO
 	}
 
-	function checkoutPaymentForm() {
-        return new Response('Display Payment Form', Response::HTTP_OK);
-	}
-
-	function squareAuthorisation() {
-        return new Response('Square Authorisation', Response::HTTP_OK);
-	}
-
-	function squareAuthorisationHandleResult() {
-        return new Response('Square Authorisation Result', Response::HTTP_OK);
-	}
 }

+ 45 - 0
src/Twig/SquareTwigRuntime.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace Bolt\Extension\Ginger\Squarepay\Twig;
+
+use Silex\Application;
+
+/**
+ * Twig runtime class that will only be invoked when one of its functions or
+ * filters are used.
+ *
+ * @author Richard Knight <rich@apewave.com>
+ */
+class SquareTwigRuntime
+{
+    private $apiClient;
+
+	public function __construct($apiClient)
+	{
+		$this->apiClient = $apiClient;
+	}
+
+	public function getCatalog()
+	{
+		try {
+			$catalogApi = new \SquareConnect\Api\CatalogApi($this->apiClient);
+			$products = $catalogApi->listCatalog();
+		} catch (Exception $e) {
+			error_log($e->getMessage());
+			return 'ERROR: '.$e->getMessage();
+		}
+		$response = [];
+		foreach($products['objects'] as $p) {
+			if ($p['type'] === 'ITEM') {
+				array_push($response, $p);
+			}
+		}
+		return $response;
+	}
+	
+	public function displayCardEntryForm() {
+		return $this->renderTemplate('@Squarefront/cardentryform.twig');
+	}
+
+
+}

+ 11 - 0
templates/backend/authorised.twig

@@ -0,0 +1,11 @@
+{% extends "_base/_page-nav.twig" %}
+
+{% block page_title __('Squarepay has been authorised') %}
+
+{% block page_main %}
+
+	<p>
+	OAuth token is <b>{{ token }}</b>.
+	</p>
+
+{% endblock %}

+ 18 - 0
templates/backend/dashboard.twig

@@ -0,0 +1,18 @@
+{% extends "_base/_page-nav.twig" %}
+
+{% block page_title __('Squarepay Dashboard') %}
+
+{% block page_main %}
+
+	<p>
+	At the moment all this enables you to do is authorise this site to use Square Payments, but I may expand functionality in the future.
+	</p>
+
+	<br>
+
+	<a href="{{ auth_link }}" class="btn btn-primary">Authorise Site</a>
+
+	<br><br>
+	{{ dump(config) }}
+
+{% endblock %}

+ 11 - 0
templates/backend/error.twig

@@ -0,0 +1,11 @@
+{% extends "_base/_page-nav.twig" %}
+
+{% block page_title __('Squarepay Error') %}
+
+{% block page_main %}
+
+	<p>
+	<b>{{ error }}</b>.
+	</p>
+
+{% endblock %}

+ 48 - 0
templates/frontend/cardentryform.twig

@@ -0,0 +1,48 @@
+<h1>Credit Card Payment Form</h1>
+
+<script>
+// For sq-paymentform.js	
+var isPaymentsPage = true;
+var applicationId = '{{ testmode ? sq_sandbox_app_id : sq_app_id }}';
+var locationId = '{{ testmode ? sq_sandbox_location_id : sq_location_id }}';
+</script>
+
+<div id="sq-ccbox">
+	<!--
+	You should replace the action attribute of the form with the path of
+	the URL you want to POST the nonce to (for example, "/process-card")
+	-->
+	<form id="nonce-form" novalidate action="/process.php" method="post">
+	<table>
+		<tbody>
+			<tr>
+				<td>Card Number</td>
+				<td><div id="sq-card-number"></div></td>
+			</tr>
+			<tr>
+				<td>CVV</td>
+				<td><div id="sq-cvv"></div></td>
+			</tr>
+			<tr>
+				<td>Expiration Date &nbsp;</td>
+				<td><div id="sq-expiration-date"></div></td>
+			</tr>
+			<tr>
+				<td>Postal Code</td>
+				<td><div id="sq-postal-code"></div></td>
+			</tr>
+			<tr>
+				<td></td>
+				<td>
+					<button class="btn btn-primary" id="sq-creditcard" onclick="requestCardNonce(event)">
+					Pay with card
+					</button>
+				</td>
+			</tr>
+		</tbody>
+	</table>
+
+	<!-- After a nonce is generated it will be assigned to this hidden input field -->
+	<input type="hidden" id="card-nonce" name="nonce">
+	</form>
+</div>