Code Monkey home page Code Monkey logo

shopify's Introduction

Shopify PHP SDK

A simple Shopify PHP SDK for private apps to easily interact with the Shopify API.
Travis Build Status

Shopify API Documentation | Packagist | Build Status

Features include:

  • ability to easily GET, PUT, POST and DELETE resources
  • process and validate incoming webhooks
  • automatic rate limiting to avoid API calls from erroring

Setup/Installation

Depends on guzzlehttp/guzzle.
Get via Composer...

Get a stable release:
composer require donutdan4114/shopify:v2020.01.*

Get the latest unstable version:
composer require donutdan4114/shopify:dev-master

API Versions

This package now includes versions that match Shopify's API version naming convention. Version format is vYYYY.MM.V# where increasing the V# will not break backwards compatibility. Check the changelog in releases to see what changes may break backwards compatability.

New in Version 2020.01

This update utilizes the new pagination system in the Shopify API. The page query param is no longer supported. The "resource pager" which automatically loops through pages will handle this change automatically for you.

If you have custom code using the page param you'll need to update your code like:

$client = new Shopify\PublicApp(/* $args */);

// Get the first 25 products.
$result = $client->get('products', ['query' => ['limit' => 25]]);

// If you're paginating in the same request directly after the first API call
// you can simply use the getNextPage() method.
if ($client->hasNextPage()){
  // Get the next 25 products.
  $result = $client->getNextPage();
}

// If you're doing multiple page requests or client requests you
// will need to keep track of the page_info params yourself.
$page_info = $client->getNextPageParams();

// To get the next page you can now just pass $page_info into the query.
// This will get the next 25 products ("limit" is automatically set).
$result = $client->get('products', ['query' => $page_info]);

Private & Public Apps

You can use this library for private or public app creation. Using private apps is easier because their is no access_token required. However, if you want to create a publicly accessible app, you must use the Public App system.

Private App

Simply instantiate a private app with the shop_domain, api_key, password, and shared_secret.

$client = new Shopify\PrivateApp($SHOPIFY_SHOP_DOMAIN, $SHOPIFY_API_KEY, $SHOPIFY_PASSWORD, $SHOPIFY_SHARED_SECRET);
$result = $client->get('shop');

Public App

You must first setup a public app. View documentation. You need an authorization URL.

session_start();
$client = new Shopify\PublicApp($_GET['shop'], $APP_API_KEY, $APP_SECRET);

// You set a random state that you will confirm later.
$random_state = 'client-id:' . $_SESSION['client_id'];

$client->authorizeUser('[MY_DOMAIN]/redirect.php', [
  'read_products',
  'write_products',
], $random_state);

At this point, the user is taken to their store to authorize the application to use their information. If the user accepts, they are taken to the redirect URL.

session_start();
$client = new Shopify\PublicApp($_GET['shop'], $APP_API_KEY, $APP_SECRET);

// Used to check request data is valid.
$client->setState('client-id:' . $_SESSION['client_id']);

if ($token = $client->getAccessToken()) {
  $_SESSION['shopify_access_token'] = $token;
  $_SESSION['shopify_shop_domain'] = $_GET['shop'];
  header("Location: dashboard.php");
}
else {
  die('invalid token');
}

It's at this point, in dashboard.php you could starting doing API request by setting the access_token.

session_start();
$client = new Shopify\PublicApp($_SESSION['shopify_shop_domain'], $APP_API_KEY, $APP_SECRET);
$client->setAccessToken($_SESSION['shopify_access_token']);
$products = $client->getProducts();

Methods

GET

Get resource information from the API.

$client = new Shopify\PrivateApp($SHOPIFY_SHOP_DOMAIN, $SHOPIFY_API_KEY, $SHOPIFY_PASSWORD, $SHOPIFY_SHARED_SECRET);
$result = $client->get('shop');

$result is a JSON decoded stdClass:

object(stdClass)#33 (1) {
  ["shop"]=>
  object(stdClass)#31 (44) {
    ["id"]=>
    int([YOUR_SHOP_ID])
    ["name"]=>
    string(15) "[YOUR_SHOP_NAME]"
    ["email"]=>
    string(22) "[YOUR_SHOP_EMAIL]"
    ["domain"]=>
    string(29) "[YOUR_SHOP_DOMAIN]"
    ...
  }
}

Get product IDs by passing query params:

$result = $client->get('products', ['query' => ['fields' => 'id']]);
foreach($result->products as $product) {
  print $product->id;
}

POST

Create new content with a POST request.

$data = ['product' => ['title' => 'my new product']];
$result = $client->post('products', $data);

PUT

Update existing content with a given ID.

$data = ['product' => ['title' => 'updated product name']];
$result = $client->put('products/' . $product_id, $data);

DELETE

Easily delete resources with a given ID.

$client->delete('products/' . $product_id);

Simple Wrapper

To make it easier to work with common API resources, there are several short-hand functions.

// Get shop info.
$shop_info = $client->getShopInfo();

// Get a specific product.
$product = $client->getProduct($product_id);

// Delete a specific product.
$client->deleteProduct($product_id);

// Create a product.
$product = $client->createProduct(['title' => 'my new product']);

// Count products easily.
$count = $client->getProductsCount(['updated_at_min' => time() - 3600]);

// Easily get all products without having to worry about page limits.
$products = $client->getProducts();

// This will fetch all products and will make multiple requests if necessary.
// You can easily supply filter arguments.
$products = $client->getProducts(['query' => ['vendor' => 'MY_VENDOR']]);

// For ease-of-use, you should use the getResources() method to automatically handle Shopify's pagination.
// This will ensure that if there are over 250 orders, you get them all returned to you.
$orders = $client->getResources('orders', ['query' => ['fields' => 'id,billing_address,customer']]);

// If efficiency and memory limits are a concern,  you can loop over results manually.
foreach ($this->client->getResourcePager('products', 25) as $product) {
  // Fetches 25 products at a time.
  // If you have 500 products, this will create 20 separate requests for you.
  // PHP memory will only be storing 25 products at a time, which keeps thing memory-efficient.
}

Parsing Incoming Webhooks

If you have a route setup on your site to accept incoming Shopify webhooks, you can easily parse the data and validate the contents. There are two ways to validate webhooks: manually, or using the client.

// Process webhook manually.
$webhook = new Shopify\IncomingWebhook($SHOPIFY_SHARED_SECRET);
try {
  $webhook->validate();
  $data = $webhook->getData();
} catch (Shopify\WebhookException $e) {
  // Errors means you should not process the webhook data.
  error_log($e->getMessage());
}

// Process webhook using the $client.
try {
  $data = $client->getIncomingWebhook($validate = TRUE);
} catch (Shopify\ClientException $e) {
  error_log($e->getMessage());
}
if (!empty($data)) {
  // Do something with the webhook data.
}

Error Handling

Any API error will throw an instance of Shopify\ClientException.

try {
  $response = $client->put('products/BAD_ID');
} catch (Shopify\ClientException $e) {
  // Get request errors.
  error_log($e->getErrors());
  // Get last response object.
  $last_response = $e->getLastResponse();
  $code = $e->getCode();
  $code = $last_response->getStatusCode();
}

API Limit Handling

This class can handle API rate limiting for you based on Shopify's "leaky bucket" algorithm. It will automatically slow down requests to not hit the rate limiter. You can disabled this with:

$client->rate_limit = FALSE;

You can put in your own rate limiting logic using the $client->getCallLimit() and $client->callLimitReached() methods.

Testing

Tests can be run with phpunit. Since the tests actually modify the connected store, you must explicitly allow tests to be run by settings SHOPIFY_ALLOW_TESTS environment variable to TRUE. Without that, you will be get a message like:

Shopify tests cannot be run.
Running Shopify tests will delete all connected store info.
Set environment variable SHOPIFY_ALLOW_TESTS=TRUE to allow tests to be run.

shopify's People

Contributors

bcampbelldev avatar donutdan4114 avatar krebstar2000 avatar str avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shopify's Issues

Public App access_token HMAC doesn't validate per Shopify specification

This seems to be a known issue in the Shopify community. I haven't figured out the best way to create a valid HMAC that would match Shopify's.

This code should work, per Shopify's own guidelines.
https://github.com/donutdan4114/shopify/blob/master/src/Shopify/PublicApp.php#L144

  public function hmacSignatureValid(array $params = []) {
    $original_hmac = $params['hmac'];
    unset($params['hmac']);
    unset($params['signature']);
    sort($params);
    $data = http_build_query($params);
    return TRUE; // @todo: Figure out why HMAC isn't working. This should work based on the docs.
    return ($original_hmac === $this->calculateHmac($data, $this->shared_secret));
  }

Embed App Issue

I am using this library for building an Public App but after getting getAccessToken() it is not redirecting user under Shop Admin with dashboard.php open under it.

Request failed when creating product with image url - even though product is created

I am just trying out this package and I am getting a request failed when trying to execute this example from the documentation:

$data = [
      "product" => [
        "title" => "Burton Custom Freestyle 151",
        "body_html" => "<strong>Good snowboard!</strong>",
        "vendor" => "Burton",
        "product_type" => "Snowboard",
        "images" => [
          [
            "src" => "https://images.blue-tomato.com/is/image/bluetomato/303918343_front.jpg-3LUB_zANeWE0vFkg1nUL6gG4MVo/Custom+166W+2020.jpg"
            ]
        ]
      ]
];

$result = $client->post('products', $data);

But when executing the same request through Postman it doesn't produce an error:

{
  "product": {
    "title": "Burton Custom Freestyle 151",
    "body_html": "<strong>Good snowboard!</strong>",
    "vendor": "Burton",
    "product_type": "Snowboard",
    "images": [
      {
        "src": "https://images.blue-tomato.com/is/image/bluetomato/303918343_front.jpg-3LUB_zANeWE0vFkg1nUL6gG4MVo/Custom+166W+2020.jpg"
      }
    ]
  }
}

In either situation the product is created in the Shopify store.

Allow for -optionally - pulling metafields for Products

Situation
At the moment the Client class only pulls Products and their respective fields. In case the Shopify Product has some metafields attached to it, they are not made available through the client's getProducts and getProduct methods.

Current workaround
It is still possible to retrieve the metafields through the get method.

Feature request
The Product or Products returned from getProduct or getProducts could - optionally - return Product objects that include a metafields property with the metafields data.

Technical suggestion
Since this is not a must-have for all use-cases of the Shopify client, it could be implemented as a Decorator class, so that an application needing access to the metafields of a product could simply do the following:

$client = new Shopify\PrivateApp($domain, $api_key, $password, $shared_secret));
$client = new MetafieldsClient($client);

Add support for Guzzle 7

Laravel 8 was released on 9/8/2020, and it increased Guzzle's minimum version requirement from ^6.3 to ^7.0.1. This unfortunately means that this package can't be used because it's requiring ~6.0.

One potential problem is that Guzzle 7 requires PHP >=7.2, so it would make a pretty big bump from your current >=5.5.0 requirement. Nobody should be using PHP 5.* at this point anyway. PHP 7.2 hasn't been actively supported since 11/30/2019, and PHP 7.3 only has active support until 12/6/2020, 2 months from now. Anybody that runs into issues with the version bump is likely having many more issues than just this package ;)

Looking through your package's source, I don't see anything in the Guzzle 6 to 7 upgrade guide that will cause any incompatibilities or problems. I don't think you'll need to change any code at all, actually. I haven't spun up a dev store to run the tests against yet though so I'm not 100% sure. I'd be happy to do that, if you'd like.

Thanks!

Composer installation does not work

installation with composer leads to error:
[InvalidArgumentException]
Could not find a version of package donutdan4114/shopify@dev matching your minimum-stability (stable). Require it with
an explicit version constraint allowing its desired stability.

Create realeases

Can a release be created when new feature is added or we risk breaks when new features are not backwards compatibe?

Shopify API version depreciated in 2020

Hi, there.

We have just received an email from Shopify stating that:-

Shopify API version 2019-04 on will be removed on April 1, 2020. Update to version 2019-07 and replace the following deprecated API calls to avoid breaking changes."

The page filter has been removed. Please use cursor-based pagination instead.

The featured field has been removed from the Collects resource

I am just wondering if this is something that is in the pipeline to fix in this repo or if it requires more developers to work on this?

Thanks

sleep on requests limit reach issue

Hey, trying to understand why you did it this way, i didn't test it yet, but it doesn't seems right

  1. if for example the call limit your app can make is 40, the callLimitReached function will return true if you still have 8 calls left,
  2. you are doing a random sleep of between 3-10 seconds if the "limit" is reached

It seems like worse case you will wait 10 seconds to make a request while you still have 8 requests available left, best case is 3 seconds.

You are doing the limit check after each request, so why don't you just check if you have 1 available request left, if you do sleep for 1 second?

Anything i'm missing why this was built this way?

Calling GraphQL endpoint throws exception on API limit checks

When calling the GraphQL endpoint (/admin/graphql.json) it'll throw an exception when trying to work out the number of API calls made/left in Client.php on the first line of this function:
protected function setCallLimitParams() { $limit_parts = explode('/', $this->last_response->getHeader(self::CALL_LIMIT_HEADER)[0]); $this->call_limit = $limit_parts[0]; $this->call_bucket = $limit_parts[1]; }
with the error

Undefined offset: 0
due to the way GraphQL returns data.
Commenting out the checks for API limits allows it to work, but then means call rate limiting is redundant

Webhooks Invalid Secret

I'm coping this error:[Sat Jul 09 05:25:27 2016] [error] [client 127.0.0.1] Invalid webhook: Invalid webhook. using PageKite locally for testing. Is there any settings I'm missing? All other requests work well, except incoming webhooks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.