├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Facades │ └── SnipcartApi.php ├── Providers │ └── SnipcartApiServiceProvider.php ├── SnipcartApi.php ├── config │ └── snipcart.php └── helpers.php └── tests └── SnipcartApiTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .DS_Store -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Mark Townsend 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | The easiest and fastest way to get up and running with Snipcart's api. 3 |

4 | 5 | 6 |

7 | Snipcart API - mtownsend/snipcart-api 8 |

9 | 10 | ## First steps 11 | 12 | * [Register an account and obtain your api key](https://app.snipcart.com/register) 13 | * [Snipcart Developer Documentation](https://docs.snipcart.com/api-reference/introduction) 14 | 15 | ## Installation 16 | 17 | Install via composer: 18 | 19 | ``` 20 | composer require mtownsend/snipcart-api 21 | ``` 22 | 23 | *This package can be used with any PHP 7.0+ application but has special support for Laravel.* 24 | 25 | ### Registering the service provider (Laravel users) 26 | 27 | For Laravel 5.4 and lower, add the following line to your ``config/app.php``: 28 | 29 | ```php 30 | /* 31 | * Package Service Providers... 32 | */ 33 | Mtownsend\SnipcartApi\Providers\SnipcartApiServiceProvider::class, 34 | ``` 35 | 36 | For Laravel 5.5 and greater, the package will auto register the provider for you. 37 | 38 | ### Using Lumen 39 | 40 | To register the service provider, add the following line to ``app/bootstrap/app.php``: 41 | 42 | ```php 43 | $app->register(Mtownsend\SnipcartApi\Providers\SnipcartApiServiceProvider::class); 44 | ``` 45 | 46 | ### Publishing the config file (Laravel users) 47 | 48 | ```` 49 | php artisan vendor:publish --provider="Mtownsend\SnipcartApi\Providers\SnipcartApiServiceProvider" 50 | ```` 51 | 52 | Once your ``snipcart.php`` has been published your to your config folder, add the api key you obtained from [Snipcart](https://app.snipcart.com/dashboard/account/credentials). If you are using Laravel and put your Snipcart api key in the config file, Laravel will automatically set your api key every time you instantiate the class through the helper or facade. 53 | 54 | ## Quick start 55 | 56 | ### Using the class 57 | 58 | ```php 59 | use Mtownsend\SnipcartApi\SnipcartApi; 60 | 61 | $orders = (new SnipcartApi($apiKey))->get()->from('/orders')->send(); 62 | ``` 63 | 64 | ### HTTP methods 65 | 66 | This package supports RESTful HTTP methods including ``GET`` (default), ``POST``, ``PUT``, ``PATCH`` and ``DELETE``. 67 | 68 | #### GET example 69 | ```php 70 | // Get a list of orders with a query string of ?limit=10&offset=5&status=Processed 71 | $orders = (new SnipcartApi($apiKey))->get()->from('/orders')->payload([ 72 | 'limit' => 10, 73 | 'offset' => 5, 74 | 'status' => 'Processed' 75 | ])->send(); 76 | ``` 77 | 78 | #### POST example 79 | ```php 80 | // Post a refund 81 | $refund = (new SnipcartApi($apiKey))->post()->payload([ 82 | 'token' => '6dc8e374-7e30-4dc5-9b68-2d605819e7f0', 83 | 'amount' => 5.00, 84 | 'comment' => "We're refunding $5 for your order." 85 | ])->to('/orders/6dc8e374-7e30-4dc5-9b68-2d605819e7f0/refunds')->send(); 86 | ``` 87 | 88 | #### PUT example 89 | ```php 90 | // Update a product 91 | $product = (new SnipcartApi($apiKey))->put()->payload([ 92 | 'stock' => 200, 93 | 'allowOutOfStockPurchases' => false 94 | ])->to('/products/3932ecd1-6508-4209-a7c6-8da4cc75590d')->send(); 95 | ``` 96 | 97 | #### DELETE example 98 | ```php 99 | // Delete an allowed domain from your Snipcart account 100 | $domain = (new SnipcartApi($apiKey))->delete()->payload([ 101 | [ 102 | [ 103 | 'domain' => 'subdomain.my-website.com', 104 | 'protocol' => 'https' 105 | ], 106 | ] 107 | ])->from('/settings/alloweddomains')->send(); 108 | ``` 109 | 110 | ### Class methods 111 | 112 | #### ->to(string '/url') or ->from(string '/url') 113 | 114 | The ``to`` or ``from`` methods are identical and only exist to make your syntax make more semantic sense (``get()->from()`` or ``post()->to()``). These methods expect to receive a relative url path for the Snipcart endpoint you're attempting to communicate with. For example, if you want to get a list of orders from ``https://app.snipcart.com/api/orders``, you would utilize your method of choice and pass it an argument of ``/orders``. 115 | 116 | Note: It does not matter if you prepend a slash, append a slash, both, or exclude both. The package gracefully handles your usage of prepended/appended slashes of the relative url. Any of these examples would be acceptable arguments: ``/orders/``, ``/orders``, ``orders/``, or ``orders``. 117 | 118 | #### ->payload(array ['key' => 'value']) or ->payload('key', 'value') 119 | 120 | The ``payload`` method allows you to pass data through with your request. 121 | 122 | If the request is a ``GET`` operation the payload will be converted to a valid query string. E.g. ``['from' => '2018-05-05', 'to' => '2019-05-05']`` will produce ``?from=2018-05-05&to=2019-05-05`` and be automatically appended to your request url. 123 | 124 | Alternatively, if your preference is to manually include your query string for ``GET`` requests you may omit the ``payload`` method entirely and append your query string to the ``to``/``from`` method. E.g. ``->get()->from('/orders?limit=10&offset=5')->send()``. 125 | 126 | If the request is a ``POST``, ``PUT``, ``PATCH`` or ``DELETE`` operation the payload will automatically be converted to json and sent in the request's body. 127 | 128 | #### ->send() 129 | 130 | The ``send`` method triggers the api call to be sent and returns the response. 131 | 132 | #### ->addHeader(string 'key', string 'value') 133 | 134 | The ``addHeader`` method accepts two arguments. The first is the header key and the second is the header value. By default this package sets the ``Accept`` and ``Content-Type`` for you. 135 | 136 | #### ->addHeaders(array ['key' => 'value']) 137 | 138 | The ``addHeaders`` method accepts an associative array of key/values to set as headers for the api request. 139 | 140 | #### ->responseCode() 141 | 142 | The ``responseCode`` method returns the http code received from the server for the request. Note: this method should only be used if you are breaking up your api call into multiple variables. 143 | 144 | #### ->successful() 145 | 146 | The ``successful`` method parses the http code received from the server and checks for any 2XX code and returns a boolean. Note: this method should only be used if you are breaking up your api call into multiple variables. 147 | 148 | ### Checking for failed api calls 149 | 150 | Snipcart's api provides graceful failure in many circumstances. If you were to make an api call to the endpoint ``/does-not-exist``, the response would be ``null``. You could easily check the value of your request object with a simple ``if`` statement before attempting to perform any logic. 151 | 152 | ```php 153 | $response = (new SnipcartApi($apiKey))->get()->from('/does-not-exist')->send(); 154 | if (!$response) { 155 | // api call failed 156 | } 157 | // else: do something with the response data... 158 | ``` 159 | 160 | Alternatively, if you prefer to split up your api call into multiple variables, the ``SnipcartApi`` class comes with a ``successful`` method. 161 | 162 | ```php 163 | $call = (new SnipcartApi($apiKey))->get()->from('/does-not-exist'); 164 | $response = $call->send(); 165 | if (!$call->successful()) { 166 | // api call failed 167 | } 168 | // else: do something with the response data... 169 | ``` 170 | 171 | ### Using the global helper (Laravel users) 172 | 173 | If you are using Laravel, this package provides a convenient helper function which is globally accessible. The package will automatically set your api key from your ``config/snipcart.php`` file. 174 | 175 | ```php 176 | snipcart()->get()->from('/orders')->send(); 177 | ``` 178 | 179 | ### Using the facade (Laravel users) 180 | 181 | If you are using Laravel, this package provides a facade. To register the facade add the following line to your ``config/app.php`` under the ``aliases`` key. The package will automatically set your api key from your ``config/snipcart.php`` file. 182 | 183 | ````php 184 | 'Snipcart' => Mtownsend\SnipcartApi\Facades\SnipcartApi::class, 185 | ```` 186 | 187 | ```php 188 | use Snipcart; 189 | 190 | Snipcart::get()->from('/orders')->send(); 191 | ``` 192 | 193 | ## Credits 194 | 195 | - Mark Townsend 196 | - [All Contributors](../../contributors) 197 | 198 | ## Testing 199 | 200 | *Tests coming soon...* 201 | 202 | You can run the tests with: 203 | 204 | ```bash 205 | ./vendor/bin/phpunit 206 | ``` 207 | 208 | ## License 209 | 210 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mtownsend/snipcart-api", 3 | "description": "A PHP (and Laravel) package to interface with the Snipcart api.", 4 | "keywords": [ 5 | "snipcart", 6 | "snip", 7 | "ecommerce", 8 | "products", 9 | "orders", 10 | "subscriptions", 11 | "customers", 12 | "abandoned", 13 | "cart", 14 | "refunds", 15 | "shipping", 16 | "api", 17 | "laravel" 18 | ], 19 | "license": "MIT", 20 | "authors": [ 21 | { 22 | "name": "Mark Townsend", 23 | "email": "mtownsend5512@gmail.com", 24 | "role": "Developer" 25 | } 26 | ], 27 | "autoload": { 28 | "psr-4": { 29 | "Mtownsend\\SnipcartApi\\": "src" 30 | }, 31 | "files": [ 32 | "src/helpers.php" 33 | ] 34 | }, 35 | "require": { 36 | "php": "~7.0|~8.0", 37 | "guzzlehttp/guzzle": "^6.0" 38 | }, 39 | "require-dev": { 40 | "phpunit/phpunit": "^6.4" 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "Mtownsend\\SnipcartApi\\Test\\": "tests/" 45 | } 46 | }, 47 | "extra": { 48 | "laravel": { 49 | "providers": [ 50 | "Mtownsend\\SnipcartApi\\Providers\\SnipcartApiServiceProvider" 51 | ] 52 | } 53 | }, 54 | "minimum-stability": "stable" 55 | } 56 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Facades/SnipcartApi.php: -------------------------------------------------------------------------------- 1 | publishes([ 18 | __DIR__ . '/../config/snipcart.php' => config_path('snipcart.php') 19 | ], 'config'); 20 | } 21 | 22 | /** 23 | * Register any application services. 24 | * 25 | * @return void 26 | */ 27 | public function register() 28 | { 29 | $this->app->bind('snipcart', function () { 30 | return new SnipcartApi(config('snipcart.api_key')); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SnipcartApi.php: -------------------------------------------------------------------------------- 1 | apiKey = $apiKey; 91 | $this->baseUrl = 'https://app.snipcart.com/api'; 92 | 93 | $this->request = new Guzzle(); 94 | $this->requestOptions = [ 95 | 'headers' => [ 96 | 'Accept' => 'application/json', 97 | 'Content-Type' => 'application/json', 98 | 'User-Agent' => "Mtownsend/SnipcartApi (github.com/mtownsend5512/snipcart-api)" 99 | ], 100 | 'auth' => [ 101 | $this->apiKey, 102 | null 103 | ], 104 | 'http_errors' => false, 105 | 'decode_content' => false 106 | ]; 107 | 108 | $this->requestOptions($options); 109 | } 110 | 111 | /** 112 | * Add a key value header 113 | * 114 | * @param string $key The header array key 115 | * @param mixed $value The header value 116 | */ 117 | public function addHeader(string $key, $value) 118 | { 119 | if ($key && $value) { 120 | $this->requestOptions['headers'][$key] = $value; 121 | } 122 | 123 | return $this; 124 | } 125 | 126 | /** 127 | * Add multiple key/value headers 128 | * 129 | * @param array $array 130 | */ 131 | public function addHeaders(array $array) 132 | { 133 | foreach ($array as $key => $value) { 134 | $this->addHeader($key, $value); 135 | } 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * Set the Snipcart api key 142 | * 143 | * @return string Snipcart api key 144 | */ 145 | public function apiKey($apiKey) 146 | { 147 | $this->apiKey = $apiKey; 148 | $this->requestOptions([ 149 | 'auth' => [ 150 | $this->apiKey, 151 | null 152 | ] 153 | ]); 154 | 155 | return $this; 156 | } 157 | 158 | /** 159 | * Set the http method to GET 160 | * 161 | * @return \Mtownsend\SnipcartApi\SnipcartApi 162 | */ 163 | public function get() 164 | { 165 | $this->httpMethod = 'GET'; 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Set the http method to POST 172 | * 173 | * @return \Mtownsend\SnipcartApi\SnipcartApi 174 | */ 175 | public function post() 176 | { 177 | $this->httpMethod = 'POST'; 178 | 179 | return $this; 180 | } 181 | 182 | /** 183 | * Set the http method to PUT 184 | * 185 | * @return \Mtownsend\SnipcartApi\SnipcartApi 186 | */ 187 | public function put() 188 | { 189 | $this->httpMethod = 'PUT'; 190 | 191 | return $this; 192 | } 193 | 194 | /** 195 | * Set the http method to PATCH 196 | * 197 | * @return \Mtownsend\SnipcartApi\SnipcartApi 198 | */ 199 | public function patch() 200 | { 201 | $this->httpMethod = 'PATCH'; 202 | 203 | return $this; 204 | } 205 | 206 | /** 207 | * Set the http method to DELETE 208 | * 209 | * @return \Mtownsend\SnipcartApi\SnipcartApi 210 | */ 211 | public function delete() 212 | { 213 | $this->httpMethod = 'DELETE'; 214 | 215 | return $this; 216 | } 217 | 218 | /** 219 | * The url endpoint to be added onto the base url 220 | * 221 | * @return \Mtownsend\SnipcartApi\SnipcartApi 222 | */ 223 | public function to($endpoint = '') 224 | { 225 | $this->endpoint = $endpoint; 226 | 227 | return $this; 228 | } 229 | 230 | /** 231 | * A proxy for the 'to' method 232 | * 233 | * @return \Mtownsend\SnipcartApi\SnipcartApi 234 | */ 235 | public function from($endpoint = '') 236 | { 237 | $this->to($endpoint); 238 | 239 | return $this; 240 | } 241 | 242 | /** 243 | * Set the api call's post encoding format 244 | * 245 | * @param string $encoding body|form_params 246 | * @return \Mtownsend\SnipcartApi\SnipcartApi 247 | */ 248 | public function postEncoding(string $encoding = 'body') 249 | { 250 | $this->postEncoding = $encoding; 251 | 252 | return $this; 253 | } 254 | 255 | /** 256 | * The payload for the request 257 | * 258 | * @param mixed string|array $payload The data to be passed along in the body or query string of the request 259 | * @param mixed string|array $value When the payload is passed as a string, it expects the payload to act as the key 260 | * @return \Mtownsend\SnipcartApi\SnipcartApi 261 | */ 262 | public function payload($payload = null, $value = null) 263 | { 264 | if ($value && is_string($payload) && $this->httpMethod == 'GET') { 265 | $payload = [$payload => $value]; 266 | } 267 | 268 | if ($payload) { 269 | $this->payload = $this->argForcedToArray($payload); 270 | } 271 | 272 | return $this; 273 | } 274 | 275 | /** 276 | * A raw payload that does not need to undergo any parsing or transformations 277 | * 278 | * @param string $payload The raw query string or json payload 279 | * @return \Mtownsend\SnipcartApi\SnipcartApi 280 | */ 281 | public function rawPayload(string $payload) 282 | { 283 | $this->payload = $payload; 284 | 285 | return $this; 286 | } 287 | 288 | /** 289 | * Set Guzzle's request options 290 | * 291 | * @param array $options 292 | * @return \Mtownsend\SnipcartApi\SnipcartApi 293 | */ 294 | public function requestOptions(array $options = []) 295 | { 296 | $this->requestOptions = array_merge($this->requestOptions, $options); 297 | 298 | return $this; 299 | } 300 | 301 | /** 302 | * Return the fully qualified url for the request 303 | * 304 | * @return string 305 | */ 306 | public function getFullRequestUrl(): string 307 | { 308 | if ($this->httpMethod == 'GET') { 309 | $this->endpoint = str_replace('/?', '?', $this->strStart(rtrim($this->endpoint, '/'), '/') . $this->payload); 310 | } 311 | 312 | if (empty($this->endpoint)) { 313 | return rtrim($this->baseUrl, '/'); 314 | } 315 | 316 | return rtrim($this->baseUrl, '/') . '/' . ltrim($this->endpoint, '/'); 317 | } 318 | 319 | /** 320 | * Send the actual API request/call and retrieve the response 321 | * 322 | * @return string 323 | */ 324 | public function send() 325 | { 326 | // Format the payload unless the payload is raw 327 | if (!is_string($this->payload)) { 328 | $this->formatPayload(); 329 | } 330 | 331 | // Send the request 332 | $request = new Guzzle(['base_uri' => $this->baseUrl]); 333 | $response = $request->request($this->httpMethod, $this->getFullRequestUrl(), array_merge($this->requestOptions, [$this->postEncoding => $this->httpMethod == 'GET' ? '' : $this->payload])); 334 | 335 | // Get the response http code 336 | $this->responseHttpCode = $response->getStatusCode(); 337 | 338 | // Parse response 339 | $this->response = json_decode((string) $response->getBody()); 340 | 341 | return $this->response; 342 | } 343 | 344 | /** 345 | * Get the api call's http response code 346 | * 347 | * @return int 348 | */ 349 | public function responseCode(): int 350 | { 351 | return (int) $this->responseHttpCode; 352 | } 353 | 354 | /** 355 | * Return the status of the api call 356 | * 357 | * @return bool 358 | */ 359 | public function successful(): bool 360 | { 361 | $responseCode = (string) $this->responseCode(); 362 | return ($responseCode[0] === '2') ? true : false; 363 | } 364 | 365 | /** 366 | * Format the payload by determining the HTTP verb and/or the specified format (query string/json) 367 | * 368 | * @return void 369 | */ 370 | protected function formatPayload(): void 371 | { 372 | if ($this->httpMethod == 'GET') { 373 | $this->payload = $this->transformArrayToQueryString($this->payload); 374 | return; 375 | } else { 376 | $this->payload = $this->transformArrayToJson($this->payload); 377 | return; 378 | } 379 | } 380 | 381 | /** 382 | * Transform an array into a json encoded string 383 | * 384 | * @param array $array The payload for a POST, PUT, or PATCH request 385 | * @return string 386 | */ 387 | protected function transformArrayToJson(array $array): string 388 | { 389 | return json_encode($array); 390 | } 391 | 392 | /** 393 | * Transform an array into a query string 394 | * 395 | * @param array $array The payload for a GET request 396 | * @return string 397 | */ 398 | protected function transformArrayToQueryString(array $array): string 399 | { 400 | $payload = $this->arrayToQueryString($array); 401 | 402 | return $payload; 403 | } 404 | 405 | /** 406 | * Clean an endpoint of prepended and trailing slashes 407 | * 408 | * @return string 409 | */ 410 | protected function cleanEndpoint($endpoint): string 411 | { 412 | return ltrim(rtrim($endpoint, '/'), '/'); 413 | } 414 | 415 | /** 416 | * Force an argument to be cast as an array 417 | * 418 | * @param mixed $arg 419 | * @return array 420 | */ 421 | protected function argForcedToArray($arg): array 422 | { 423 | if (is_array($arg)) { 424 | return $arg; 425 | } 426 | 427 | if (is_object($arg) && method_exists($arg, 'toArray')) { 428 | return $arg->toArray(); 429 | } 430 | 431 | return [$arg]; 432 | } 433 | 434 | /** 435 | * Convert an array to a valid query string 436 | * 437 | * @param array $array 438 | * @return string 439 | */ 440 | protected function arrayToQueryString(array $array): string 441 | { 442 | $queryString = http_build_query($array); 443 | 444 | if (empty($queryString)) { 445 | return ''; 446 | } 447 | 448 | return '?' . $queryString; 449 | } 450 | 451 | /** 452 | * Determine if an array is associative 453 | * 454 | * @param array @array 455 | * @return boolean 456 | */ 457 | protected function isAssocArray(array $array): bool 458 | { 459 | $keys = array_keys($array); 460 | 461 | return array_keys($keys) !== $keys; 462 | } 463 | 464 | /** 465 | * Begin a string with a single instance of a given value 466 | * 467 | * @param string $value 468 | * @param string $prefix 469 | * @return string 470 | */ 471 | protected function strStart($value, $prefix): string 472 | { 473 | $quoted = preg_quote($prefix, '/'); 474 | 475 | return $prefix . preg_replace('/^(?:' . $quoted . ')+/u', '', $value); 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /src/config/snipcart.php: -------------------------------------------------------------------------------- 1 | '', 9 | ]; -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 |