Skip to content

Instantly share code, notes, and snippets.

@jmc734
Created August 12, 2012 01:19
Show Gist options
  • Save jmc734/3328652 to your computer and use it in GitHub Desktop.
Save jmc734/3328652 to your computer and use it in GitHub Desktop.
Shopify Batch Discount Automation (Create, Enable/Disable, Delete)
<?php
/**
* Discount
*
* Create, modify, and delete Shopify discounts
*
* PHP version 5
*
* @author Jacob McDonald <jmc734@gmail.com>
*/
class BatchDiscount {
private $base;
private $login;
private $password;
private $curl;
private $authToken;
/**
* Constructor
*
* initialize Shopify session
*
* @param string $login email used to login to Shopify
* @param string $password password used to login to Shopify
* @param string $base admin panel URL (e.g. https://test-store.myshopify.com/admin)
* @param string $cookies filepath to store cookies in
*
* @return boolean true if successfully logged in, else, exception thrown
* @access public
*/
public function __construct($login, $password, $base, $cookies) {
// Set properties
$this->login = $login;
$this->password = $password;
$this->base = $base;
$this->cookies = $cookies;
// Add login and password to post
$post = array(
'login' => $login,
'password' => $password
);
// Send login request to Shopify
$ch = $this->initCurl();
curl_setopt($ch, CURLOPT_URL, $base.'/auth/login');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_exec($ch);
// Check if CURL returned an error
if(curl_errno($ch) !== 0){
$exception = new Exception('Login Request Failed: '.curl_error($ch).' ('.curl_errno($ch).')');
curl_close($ch);
throw $exception;
}
// Check if HTTP error returned
if(curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200){
curl_close($ch);
throw new Exception('HTTP Error: '.curl_getinfo($ch, CURLINFO_HTTP_CODE));
}
// Check if login was not successful
if(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) !== $base){
curl_close($ch);
throw new Exception('Login Failure: Wrong email or password');
}
curl_close($ch);
return true;
}
/**
* Create
*
* create new discounts
*
* @param array $codes codes for the new discounts, array of strings
* @param string $discountType type of discount (fixed_amount, percentage, or shipping)
* @param int,float $value value of discount ($ or %). only for fixed_amount or percentage
* @param string $start activation date (YYYY-MM-DD). leave blank to activate immediately
* @param string $end deactivation date (YYYY-MM-DD). leave blank to keep alive until manual deactivation
* @param int $usageLimit number of uses allowed. pass empty string for unlimited
* @param string $resourceType type of resource that the discount is applied to (minimum_order_amount, custom_collection, product, customer_group). leave blank to apply to all orders
* @param int,float $minOrderAmount minimum order value required to apply discount (only if $resourceType is minimum_order_amount)
* @param string $resourceId ID of the resource the discount applies to
*
* @return string ID of new discount if discount created successfully, else, exception thrown
* @access public
*/
public function create($codes, $discountType, $value, $start = '', $end = '', $usageLimit = '', $resourceType = '', $minOrderAmount = 0, $resourceId = '') {
$mh = curl_multi_init();
$handles = array();
for($i=0; $i<count($codes); $i++){
$code = $codes[$i];
// Check for valid Resource Type and corresponding argument (Minimum Order Amount, Resource ID)
switch($resourceType){
case(''): break;
case('minimum_order_amount'):
if($minOrderAmount <= 0){
throw new Exception('Argument Error: Must supply minimum order amount');
}
break;
case('custom_collection'):
case('product'):
case('customer_group'):
if($resourceId === ''){
throw new Exception('Argument Error: Must supply an Resource ID');
}
break;
default:
throw new Exception('Argument Error: Invalid Resource Type supplied');
}
// Build post string
$post = 'utf8=%E2%9C%93&authenticity_token='.$this->getAuthenticityToken().'&discount%5Bcode%5D='.$code.'&discount%5Bdiscount_type%5D='.$discountType.'&discount%5Bvalue%5D='.$value.'&discount%5Bapplies_to_resource%5D='.$resourceType.'&discount%5Bminimum_order_amount%5D='.$minOrderAmount.'&applies_to_product='.$resourceId.'&applies_to_collection='.$resourceId.'&applies_to_customer_group='.$resourceId.'&discount%5Bstarts_at%5D='.$start.'&discount%5Bends_at%5D='.$end.'&discount%5Busage_limit%5D='.$usageLimit.'&commit=Create%20discount&page=1';
// Send request to Shopify to create Discount
$ch = $this->initCurl();
curl_setopt($ch, CURLOPT_URL, $this->base.'/discounts');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
// Add cURL handle to curl_multi handle
curl_multi_add_handle($mh,$ch);
// Add reference to cURL handle to array with the associated code as the key, used to get the Shopify ID later
$handles[$code] = &$ch;
}
// Wait for all requests to finish
$running = null;
do {
curl_multi_exec($mh,$running);
} while($running > 0);
// Extract new Discount ID from returns
// TODO: Current method of extracting ID is terrible (unreliable). Multiple requests come back with the same ID.
// Somehow find the current code in the response then find and extract the associated ID
$return = array();
$match = array();
foreach($handles as $code=>$ch){
preg_match('@discount-([^"]*)"@', curl_multi_getcontent($ch), $match);
print_object(curl_multi_getcontent($ch).'<br/><br/><br/><br/>');
if(count($match) == 2){
$return[$code] = $match[1];
}
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
return $return;
}
/**
* Delete
*
* delete existing discounts
*
* @param array $ids IDs of the discounts to delete
*
* @return array IDs of discounts that could not be modified
* @access public
*/
public function delete($ids){
return $this->modify($ids, 'authenticity_token='.$this->getAuthenticityToken().'&_method=delete');
}
/**
* Disable
*
* disable existing discounts
*
* @param array $ids IDs of the discounts to disable
*
* @return array IDs of discounts that could not be modified
* @access public
*/
public function disable($ids){
return $this->modify($ids, 'authenticity_token='.$this->getAuthenticityToken(), '/disable');
}
/**
* Enable
*
* enable existing discounts
*
* @param array $ids IDs of the discounts to disable
*
* @return array IDs of discounts that could not be modified
* @access public
*/
public function enable($ids){
return $this->modify($ids, 'authenticity_token='.$this->getAuthenticityToken(), '/enable');
}
/**
* Destructor
*
* kill Shopify session
*
* @access public
*/
public function __destruct() {
$ch = $this->initCurl();
curl_setopt($ch, CURLOPT_URL, $this->base.'/auth/logout');
curl_exec($ch);
curl_close($ch);
}
/**
* Modify
*
* modify existing discounts
*
* @param string $uri string to append to URL
* @param string $post post string to send in request
*
* @return string response from Shopify if successful, else, exception thrown
* @access private
*/
private function modify($ids, $post, $append = ''){
$mh = curl_multi_init();
$handles = array();
foreach($ids as $id){
$ch = $this->initCurl();
curl_setopt($ch, CURLOPT_URL, $this->base.'/discounts/'.$id.$append);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_multi_add_handle($mh,$ch);
$handles[$id] = $ch;
}
$running = null;
do {
curl_multi_exec($mh,$running);
} while($running > 0);
$failed = array();
foreach($handles as $id=>$ch){
if(!preg_match('@Messenger.notice\("([^"]*)"@', curl_multi_getcontent($ch))){
$failed[] = $id;
}
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
return $failed;
}
/**
* Initialize cURL
*
* create a new cURL handle with some presets
*
* @return cURL handle initialized cURL handle
* @access private
*/
private function initCurl() {
if(empty($this->curl)){
$options = array(
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_COOKIEJAR => $this->cookies,
CURLOPT_COOKIEFILE => $this->cookies,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_RETURNTRANSFER => true
);
$this->curl = curl_init();
curl_setopt_array($this->curl, $options);
}
return curl_copy_handle($this->curl);
}
/**
* Get Authenticity Token
*
* get token required in post to create/modify discounts
*
* @return string authenticity token
* @access private
*/
private function getAuthenticityToken() {
if(empty($this->authToken)){
// Load Promotion page
$ch = $this->initCurl();
curl_setopt($ch, CURLOPT_URL, $this->base.'/marketing');
curl_setopt($ch, CURLOPT_HEADER, false);
$response = curl_exec($ch);
curl_close($ch);
// Extract Authenticity Token from the response HTML
$match = array();
preg_match('@name="authenticity_token" type="hidden" value="([^"]*)"@', $response, $match);
if(count($match)<2){
throw new Exception('Parse Error: Could not extract Authenticity Token');
}
// Encode the token for passing in the post
$this->authToken = urlencode($match[1]);
}
return $this->authToken;
}
}
?>
@dcworldwide
Copy link

@jmc734 Neat! But does this code still work? Don't you need a Shopify Plus account?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment