squareup.php (15012B)
1 <?php 2 3 class Squareup { 4 private $session; 5 private $url; 6 private $config; 7 private $log; 8 private $customer; 9 private $currency; 10 private $registry; 11 12 const API_URL = 'https://connect.squareup.com'; 13 const API_VERSION = 'v2'; 14 const ENDPOINT_ADD_CARD = 'customers/%s/cards'; 15 const ENDPOINT_AUTH = 'oauth2/authorize'; 16 const ENDPOINT_CAPTURE_TRANSACTION = 'locations/%s/transactions/%s/capture'; 17 const ENDPOINT_CUSTOMERS = 'customers'; 18 const ENDPOINT_DELETE_CARD = 'customers/%s/cards/%s'; 19 const ENDPOINT_GET_TRANSACTION = 'locations/%s/transactions/%s'; 20 const ENDPOINT_LOCATIONS = 'locations'; 21 const ENDPOINT_REFRESH_TOKEN = 'oauth2/clients/%s/access-token/renew'; 22 const ENDPOINT_REFUND_TRANSACTION = 'locations/%s/transactions/%s/refund'; 23 const ENDPOINT_TOKEN = 'oauth2/token'; 24 const ENDPOINT_TRANSACTIONS = 'locations/%s/transactions'; 25 const ENDPOINT_VOID_TRANSACTION = 'locations/%s/transactions/%s/void'; 26 const PAYMENT_FORM_URL = 'https://js.squareup.com/v2/paymentform'; 27 const SCOPE = 'MERCHANT_PROFILE_READ PAYMENTS_READ SETTLEMENTS_READ CUSTOMERS_READ CUSTOMERS_WRITE'; 28 const VIEW_TRANSACTION_URL = 'https://squareup.com/dashboard/sales/transactions/%s/by-unit/%s'; 29 const SQUARE_INTEGRATION_ID = 'sqi_65a5ac54459940e3600a8561829fd970'; 30 31 public function __construct($registry) { 32 $this->session = $registry->get('session'); 33 $this->url = $registry->get('url'); 34 $this->config = $registry->get('config'); 35 $this->log = $registry->get('log'); 36 $this->customer = $registry->get('customer'); 37 $this->currency = $registry->get('currency'); 38 $this->registry = $registry; 39 } 40 41 public function api($request_data) { 42 $url = self::API_URL; 43 44 if (empty($request_data['no_version'])) { 45 $url .= '/' . self::API_VERSION; 46 } 47 48 $url .= '/' . $request_data['endpoint']; 49 50 $curl_options = array( 51 CURLOPT_URL => $url, 52 CURLOPT_RETURNTRANSFER => true 53 ); 54 55 if (!empty($request_data['content_type'])) { 56 $content_type = $request_data['content_type']; 57 } else { 58 $content_type = 'application/json'; 59 } 60 61 // handle method and parameters 62 if (isset($request_data['parameters']) && is_array($request_data['parameters']) && count($request_data['parameters'])) { 63 $params = $this->encodeParameters($request_data['parameters'], $content_type); 64 } else { 65 $params = null; 66 } 67 68 switch ($request_data['method']) { 69 case 'GET' : 70 $curl_options[CURLOPT_POST] = false; 71 72 if (is_string($params)) { 73 $curl_options[CURLOPT_URL] .= ((strpos($url, '?') === false) ? '?' : '&') . $params; 74 } 75 76 break; 77 case 'POST' : 78 $curl_options[CURLOPT_POST] = true; 79 80 if ($params !== null) { 81 $curl_options[CURLOPT_POSTFIELDS] = $params; 82 } 83 84 break; 85 default : 86 $curl_options[CURLOPT_CUSTOMREQUEST] = $request_data['method']; 87 88 if ($params !== null) { 89 $curl_options[CURLOPT_POSTFIELDS] = $params; 90 } 91 92 break; 93 } 94 95 // handle headers 96 $added_headers = array(); 97 98 if (!empty($request_data['auth_type'])) { 99 if (empty($request_data['token'])) { 100 if ($this->config->get('payment_squareup_enable_sandbox')) { 101 $token = $this->config->get('payment_squareup_sandbox_token'); 102 } else { 103 $token = $this->config->get('payment_squareup_access_token'); 104 } 105 } else { 106 // custom token trumps sandbox/regular one 107 $token = $request_data['token']; 108 } 109 110 $added_headers[] = 'Authorization: ' . $request_data['auth_type'] . ' ' . $token; 111 } 112 113 if (!is_array($params)) { 114 // curl automatically adds Content-Type: multipart/form-data when we provide an array 115 $added_headers[] = 'Content-Type: ' . $content_type; 116 } 117 118 if (isset($request_data['headers']) && is_array($request_data['headers'])) { 119 $curl_options[CURLOPT_HTTPHEADER] = array_merge($added_headers, $request_data['headers']); 120 } else { 121 $curl_options[CURLOPT_HTTPHEADER] = $added_headers; 122 } 123 124 $this->debug("SQUAREUP DEBUG START..."); 125 $this->debug("SQUAREUP ENDPOINT: " . $curl_options[CURLOPT_URL]); 126 $this->debug("SQUAREUP HEADERS: " . print_r($curl_options[CURLOPT_HTTPHEADER], true)); 127 $this->debug("SQUAREUP PARAMS: " . $params); 128 129 // Fire off the request 130 $ch = curl_init(); 131 curl_setopt_array($ch, $curl_options); 132 $result = curl_exec($ch); 133 134 if ($result) { 135 $this->debug("SQUAREUP RESULT: " . $result); 136 137 curl_close($ch); 138 139 $return = json_decode($result, true); 140 141 if (!empty($return['errors'])) { 142 throw new \Squareup\Exception($this->registry, $return['errors']); 143 } else { 144 return $return; 145 } 146 } else { 147 $info = curl_getinfo($ch); 148 149 curl_close($ch); 150 151 throw new \Squareup\Exception($this->registry, "CURL error. Info: " . print_r($info, true), true); 152 } 153 } 154 155 public function verifyToken($access_token) { 156 try { 157 $request_data = array( 158 'method' => 'GET', 159 'endpoint' => self::ENDPOINT_LOCATIONS, 160 'auth_type' => 'Bearer', 161 'token' => $access_token 162 ); 163 164 $this->api($request_data); 165 } catch (\Squareup\Exception $e) { 166 if ($e->isAccessTokenRevoked() || $e->isAccessTokenExpired()) { 167 return false; 168 } 169 170 // In case some other error occurred 171 throw $e; 172 } 173 174 return true; 175 } 176 177 public function authLink($client_id) { 178 $state = $this->authState(); 179 180 $redirect_uri = str_replace('&', '&', $this->url->link('extension/payment/squareup/oauth_callback', 'user_token=' . $this->session->data['user_token'], true)); 181 182 $this->session->data['payment_squareup_oauth_redirect'] = $redirect_uri; 183 184 $params = array( 185 'client_id' => $client_id, 186 'response_type' => 'code', 187 'scope' => self::SCOPE, 188 'locale' => 'en-US', 189 'session' => 'false', 190 'state' => $state, 191 'redirect_uri' => $redirect_uri 192 ); 193 194 return self::API_URL . '/' . self::ENDPOINT_AUTH . '?' . http_build_query($params); 195 } 196 197 public function fetchLocations($access_token, &$first_location_id) { 198 $request_data = array( 199 'method' => 'GET', 200 'endpoint' => self::ENDPOINT_LOCATIONS, 201 'auth_type' => 'Bearer', 202 'token' => $access_token 203 ); 204 205 $api_result = $this->api($request_data); 206 207 $locations = array_filter($api_result['locations'], array($this, 'filterLocation')); 208 209 if (!empty($locations)) { 210 $first_location = current($locations); 211 $first_location_id = $first_location['id']; 212 } else { 213 $first_location_id = null; 214 } 215 216 return $locations; 217 } 218 219 public function exchangeCodeForAccessToken($code) { 220 $request_data = array( 221 'method' => 'POST', 222 'endpoint' => self::ENDPOINT_TOKEN, 223 'no_version' => true, 224 'parameters' => array( 225 'client_id' => $this->config->get('payment_squareup_client_id'), 226 'client_secret' => $this->config->get('payment_squareup_client_secret'), 227 'redirect_uri' => $this->session->data['payment_squareup_oauth_redirect'], 228 'code' => $code 229 ) 230 ); 231 232 return $this->api($request_data); 233 } 234 235 public function debug($text) { 236 if ($this->config->get('payment_squareup_debug')) { 237 $this->log->write($text); 238 } 239 } 240 241 public function refreshToken() { 242 $request_data = array( 243 'method' => 'POST', 244 'endpoint' => sprintf(self::ENDPOINT_REFRESH_TOKEN, $this->config->get('payment_squareup_client_id')), 245 'no_version' => true, 246 'auth_type' => 'Client', 247 'token' => $this->config->get('payment_squareup_client_secret'), 248 'parameters' => array( 249 'access_token' => $this->config->get('payment_squareup_access_token') 250 ) 251 ); 252 253 return $this->api($request_data); 254 } 255 256 public function addCard($square_customer_id, $card_data) { 257 $request_data = array( 258 'method' => 'POST', 259 'endpoint' => sprintf(self::ENDPOINT_ADD_CARD, $square_customer_id), 260 'auth_type' => 'Bearer', 261 'parameters' => $card_data 262 ); 263 264 $result = $this->api($request_data); 265 266 return array( 267 'id' => $result['card']['id'], 268 'card_brand' => $result['card']['card_brand'], 269 'last_4' => $result['card']['last_4'] 270 ); 271 } 272 273 public function deleteCard($square_customer_id, $card) { 274 $request_data = array( 275 'method' => 'DELETE', 276 'endpoint' => sprintf(self::ENDPOINT_DELETE_CARD, $square_customer_id, $card), 277 'auth_type' => 'Bearer' 278 ); 279 280 return $this->api($request_data); 281 } 282 283 public function addLoggedInCustomer() { 284 $request_data = array( 285 'method' => 'POST', 286 'endpoint' => self::ENDPOINT_CUSTOMERS, 287 'auth_type' => 'Bearer', 288 'parameters' => array( 289 'given_name' => $this->customer->getFirstName(), 290 'family_name' => $this->customer->getLastName(), 291 'email_address' => $this->customer->getEmail(), 292 'phone_number' => $this->customer->getTelephone(), 293 'reference_id' => $this->customer->getId() 294 ) 295 ); 296 297 $result = $this->api($request_data); 298 299 return array( 300 'customer_id' => $this->customer->getId(), 301 'sandbox' => $this->config->get('payment_squareup_enable_sandbox'), 302 'square_customer_id' => $result['customer']['id'] 303 ); 304 } 305 306 public function addTransaction($data) { 307 if ($this->config->get('payment_squareup_enable_sandbox')) { 308 $location_id = $this->config->get('payment_squareup_sandbox_location_id'); 309 } else { 310 $location_id = $this->config->get('payment_squareup_location_id'); 311 } 312 313 $request_data = array( 314 'method' => 'POST', 315 'endpoint' => sprintf(self::ENDPOINT_TRANSACTIONS, $location_id), 316 'auth_type' => 'Bearer', 317 'parameters' => $data 318 ); 319 320 $result = $this->api($request_data); 321 322 return $result['transaction']; 323 } 324 325 public function getTransaction($location_id, $transaction_id) { 326 $request_data = array( 327 'method' => 'GET', 328 'endpoint' => sprintf(self::ENDPOINT_GET_TRANSACTION, $location_id, $transaction_id), 329 'auth_type' => 'Bearer' 330 ); 331 332 $result = $this->api($request_data); 333 334 return $result['transaction']; 335 } 336 337 public function captureTransaction($location_id, $transaction_id) { 338 $request_data = array( 339 'method' => 'POST', 340 'endpoint' => sprintf(self::ENDPOINT_CAPTURE_TRANSACTION, $location_id, $transaction_id), 341 'auth_type' => 'Bearer' 342 ); 343 344 $this->api($request_data); 345 346 return $this->getTransaction($location_id, $transaction_id); 347 } 348 349 public function voidTransaction($location_id, $transaction_id) { 350 $request_data = array( 351 'method' => 'POST', 352 'endpoint' => sprintf(self::ENDPOINT_VOID_TRANSACTION, $location_id, $transaction_id), 353 'auth_type' => 'Bearer' 354 ); 355 356 $this->api($request_data); 357 358 return $this->getTransaction($location_id, $transaction_id); 359 } 360 361 public function refundTransaction($location_id, $transaction_id, $reason, $amount, $currency, $tender_id) { 362 $request_data = array( 363 'method' => 'POST', 364 'endpoint' => sprintf(self::ENDPOINT_REFUND_TRANSACTION, $location_id, $transaction_id), 365 'auth_type' => 'Bearer', 366 'parameters' => array( 367 'idempotency_key' => uniqid(), 368 'tender_id' => $tender_id, 369 'reason' => $reason, 370 'amount_money' => array( 371 'amount' => $this->lowestDenomination($amount, $currency), 372 'currency' => $currency 373 ) 374 ) 375 ); 376 377 $this->api($request_data); 378 379 return $this->getTransaction($location_id, $transaction_id); 380 } 381 382 public function lowestDenomination($value, $currency) { 383 $power = $this->currency->getDecimalPlace($currency); 384 385 $value = (float)$value; 386 387 return (int)($value * pow(10, $power)); 388 } 389 390 public function standardDenomination($value, $currency) { 391 $power = $this->currency->getDecimalPlace($currency); 392 393 $value = (int)$value; 394 395 return (float)($value / pow(10, $power)); 396 } 397 398 protected function filterLocation($location) { 399 if (empty($location['capabilities'])) { 400 return false; 401 } 402 403 return in_array('CREDIT_CARD_PROCESSING', $location['capabilities']); 404 } 405 406 protected function encodeParameters($params, $content_type) { 407 switch ($content_type) { 408 case 'application/json' : 409 return json_encode($params); 410 case 'application/x-www-form-urlencoded' : 411 return http_build_query($params); 412 default : 413 case 'multipart/form-data' : 414 // curl will handle the params as multipart form data if we just leave it as an array 415 return $params; 416 } 417 } 418 419 protected function authState() { 420 if (!isset($this->session->data['payment_squareup_oauth_state'])) { 421 $this->session->data['payment_squareup_oauth_state'] = bin2hex(openssl_random_pseudo_bytes(32)); 422 } 423 424 return $this->session->data['payment_squareup_oauth_state']; 425 } 426 }