Skip to content

Instantly share code, notes, and snippets.

@oneillo
Created February 18, 2017 02:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oneillo/f5c034cba04e0ec1f175a3a8377b9d64 to your computer and use it in GitHub Desktop.
Save oneillo/f5c034cba04e0ec1f175a3a8377b9d64 to your computer and use it in GitHub Desktop.
Perl script to create Shopify discount codes
#!/usr/bin/perl
# v21
use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Request;
use JSON::XS;
use JSON;
use POSIX qw(ceil strftime);
use Time::HiRes qw(usleep);
use Time::Local;
use DateTime::Format::Strptime;
# GLOBAL VARIABLES
# I KNOW THIS IS BAD...SO SHOOT ME
my $timestamp = `date +"%Y%m%d%H%M"`; # Timestamp captures when the script is run and is used to name the output files that it creates
chomp($timestamp);
my $scriptVersion = "21";
my $discountStartDate = `date +"%Y-%m-%d"`;
chomp($discountStartDate);
my $discountEndDate;
my $discountAmount;
my $discountType;
my $discountCode = "";
my $discountCount = 1;
my $discountUsageLimit;
my $minOrderAmount=0;
my $newCustomerGroup = '756190596'; # This is the Shopify code for a custom group created to include any email accounts that have 0 orders. You might have to update this by getting your value from the URL for that group
my $limitToNewCustomers = 0;
my $appliesOnce = 'false';
my $fileNameForCodes;
#
# parse_args READS IN OPTIONS THAT ARE PASSED TO THE SCRIPT WHEN IT IS RUN, I.E. get_shopify_data.pl -s 2015-07-01 -e 2015-07-31
# THE ONLY OPTIONS RIGHT NOW ARE -H FOR HELP AND -S AND -E FOR THE START AND END DATES TO GET DATA FOR
sub parse_args()
{
my $arg;
my @argv = @ARGV; #THIS GETS THE ARRAY OF OPTIONS PASSED WHEN THE SCRIPT WAS RUN
# CALL THE HELP FUNCTION IF NO OPTIONS WERE PASSED WHEN IT WAS RUN
if($#argv == "-1")
{
help();
}
# GO THROUGH THE OPTIONS AND PULL OUT THE START AND END DATE
while(@argv)
{
$arg = shift(@argv);
if($arg eq "-s" || $arg eq "-S")
{
if($#argv == "-1")
{
die "Error: Missing start date after -s option\n";
}
$discountStartDate = shift(@argv);
}
elsif($arg eq "-e" || $arg eq "-E")
{
if($#argv == "-1")
{
die "Error: Missing end date after -e option\n";
}
$discountEndDate = shift(@argv);
}
elsif($arg eq "-v" || $arg eq "-V")
{
if($#argv == "-1")
{
die "Error: Missing the discount amount after -v option\n";
}
$discountAmount = shift(@argv);
}
elsif($arg eq "-f" || $arg eq "-F")
{
if($#argv == "-1")
{
die "Error: Missing the file name after -f option\n";
}
$fileNameForCodes = shift(@argv);
}
elsif($arg eq "-n" || $arg eq "-N")
{
if($#argv == "-1")
{
die "Error: Missing the number of discount codes to create after -N option\n";
}
$discountCount = shift(@argv);
}
elsif($arg eq "-c" || $arg eq "-C")
{
if($#argv == "-1")
{
die "Error: Missing the discount code after -c option\n";
}
$discountCode = shift(@argv);
}
elsif($arg eq "-l" || $arg eq "-L")
{
if($#argv == "-1")
{
die "Error: Missing the discount usage limit after -l option\n";
}
$discountUsageLimit = shift(@argv);
}
elsif($arg eq "-m" || $arg eq "-M")
{
if($#argv == "-1")
{
die "Error: Missing the min order amount after -m option\n";
}
$minOrderAmount = shift(@argv);
}
elsif($arg eq "-d" || $arg eq "-D")
{
if($#argv == "-1")
{
die "Error: Missing the discount type after -d option\n";
}
my $tempDT = shift(@argv);
if(lc($tempDT) eq "p")
{
$discountType = "percentage";
}
else
{
$discountType = "fixed_amount";
}
}
elsif($arg eq "-o" || $arg eq "-O")
{
$appliesOnce = "true";
}
elsif($arg eq "-y" || $arg eq "-Y")
{
$limitToNewCustomers = 1;
}
elsif($arg eq "-h" || $arg eq "-H")
{
help();
}
else
{
die "Illegal option: $arg.\n";
}
}
}
#
# HELP FUNCTION THAT OUTPUTS THE OPTIONS THAT NEED TO BE PASSED TO THE SCRIPT WHEN IT'S RUN
sub help
{
print "Usage:\n",
" create_discount_codes.pl <OPTIONS>\n",
" Required:\n",
" -d TYPE : Discount Type: f or p (fixed or percentage) \n",
" -v VALUE : Amount to discount, either fixed dollars or percentage, e.g. 50.00 or 15.0\n",
" -s START_DATE : The first day in the period you want to pull data for. In YYYY-MM-DD format, e.g. 2015-07-01 \n",
" -e END_DATE : The last day in the period you want to pull data for. In YYYY-MM-DD format, e.g. 2015-07-31 \n",
" -c CODE : Discount code\n",
" -f FILENAME : file in same directory as script with codes to create on separate lines. This supercedes -c CODE\n",
" -l LIMIT : Number of times code can be used\n",
" -m MINORDERAMT : Minimum amount customer needs to spend on order to use discount\n",
" -n # OF DISCOUNTS : Number of discount codes to generate\n",
" -o : To set the code to apply once per customer\n",
" -y : To apply to new customers only\n",
" -h : See the help info \n";
die "\n";
}
# RANDOM STRING GENERATOR
# USE THIS TO CREATE DISCOUNT CODES IF THEY AREN'T PROVIDED VIA A FILENAME OR WHEN RUNNING THE COMMAND'
sub rndStr{ join'', @_[ map{ rand @_ } 1 .. shift ] }
#
# THE MAIN FUNCTION THAT LOGS INTO THE SHOPIFY ACCOUNT, CREATES THE DISCOUNT CODES, AND RECORDS THE CODES TO A FILE IN THE SAME FOLDER WHERE THE SCRIPT IS LOCATED
sub create_discount_codes
{
#
# ARRAY OF SHOPIFY STORE LOGIN INFORMATION
# INCLUDE THE CREDENTIALS FOR ALL OF THE SHOPIFY STORES THAT YOU WANT TO PULL DATA FROM
# STORE NAME (WHATEVER YOU WANT TO CALL EACH ONE), THE STORE'S URL, THE SHOPIFY API KEY, AND THE SHOPIFY API PASSWORD
#
my @shopifyStores =
(
{
#INFO FOR THE FIRST SHOPIFY STORE/ACCOUNT
name => 'CAN BE ANY NAME YOU WANT TO ASSIGN THE SHOPIFY STORE', # IF YOU HAVE MULTIPLE STOREFRONTS, THIS LETS YOU ASSIGN A NAME TO IDENTIFY EACH ONE. CAN BE ANY STRING VALUE THAT YOU WANT
url => 'URL TO THE SHOPIFY STORE', # URL SHOULD BE SOMETHING LIKE HELLOWORLD.SHOPIFY.COM
apiKey => 'APIKEY', # REPLACE APIKEY WITH THE ACTUAL API KEY FROM SHOPIFY
apiPass => 'APIPASS' # REPLACE APIPASS WITH THE ACTUAL API PASSWORD FROM SHOPIFY
}
);
#
# GO THROUGH EACH STORE DEFINED IN THE ARRAY ABOVE
foreach my $shopifyStore (@shopifyStores)
{
my $storeUrl = $shopifyStore->{'url'};
my $apiKey = $shopifyStore->{'apiKey'};
my $apiPass = $shopifyStore->{'apiPass'};
#
# Format the start and end dates
my $parser = DateTime::Format::Strptime->new(pattern => '%Y-%m-%d');
my $origEnd = $discountEndDate;
my $origStartDate = $parser->parse_datetime($discountStartDate);
#
# Create the variable with the date the discount codes should expire if it was provided
if (defined $discountEndDate)
{
my $origEndDate = $parser->parse_datetime($discountEndDate);
$discountEndDate = $origEndDate->strftime('%Y-%m-%d');
}
#
# If you passed a file that has discount codes that you want to use in it, read the file and create each of the codes
if(defined $fileNameForCodes)
{
open my $FILE, $fileNameForCodes or die $!;
while (my $discCode = <$FILE>)
{
chomp($discCode);
print "Discount Code = $discCode\n";
my %discountCodeInfo;
if($limitToNewCustomers)
{
%discountCodeInfo = (discount=>{discount_type=>$discountType,code=>$discCode,starts_at=>$discountStartDate,ends_at=>$discountEndDate,usage_limit=>$discountUsageLimit, applies_once_per_customer=>$appliesOnce,applies_to_resource=>"customer_saved_search",applies_to_id=>$newCustomerGroup,value=>$discountAmount,minimum_order_amount=>$minOrderAmount});
}
else
{
%discountCodeInfo = (discount=>{discount_type=>$discountType,code=>$discCode,starts_at=>$discountStartDate,ends_at=>$discountEndDate,usage_limit=>$discountUsageLimit,applies_once_per_customer=>$appliesOnce,value=>$discountAmount,minimum_order_amount=>$minOrderAmount});
}
my $json = JSON->new;
my $discdata = $json->encode(\%discountCodeInfo);
# http://user:password@www.domain.com/script.pl
# CREATE THE REQUEST AND PASS IT TO SHOPIFY TO CREATE THE DISCOUNT CODE
my $URL = "https://$apiKey:$apiPass\@$storeUrl/admin/discounts.json";
my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 });
my $header = HTTP::Request->new('POST' => $URL);
my $request = HTTP::Request->new('POST', $URL, $header, $discdata);
$request->content_type('application/json');
# GET THE RESPONSE CODE BACK TO CHECK IF IT WAS SUCCESSFUL
my $response = $ua->request($request);
if ($response->is_success)
{
my $arrayref = decode_json($response->content);
my $headerContent = $response->header('X-Shopify-Shop-Api-Call-Limit');
my @apiLimitBucket = split("/",$headerContent);
if($apiLimitBucket[0] > $apiLimitBucket[1] - 20)
{
usleep(18000000);
}
}
else
{
print "HTTP POST error code: ", $response->code, "\n";
print "HTTP POST error message: ", $response->message, "\n";
}
}
close $FILE;
}
else
{
#
# CREATE THE LOG FILE NAMES WHERE THE GENERATED DISCOUNT CODES WILL BE SAVED
my $discountCodesFile;
$discountCodesFile = "Shopify_discount_codes_$timestamp\_$scriptVersion.csv"; # CSV file where the summary metrics are stored when the script finishes
# AND THEN OPEN THE FILE TO WRITE DATA TO IT
open(my $summaryFile, '>', $discountCodesFile) or die "Could not open file '$discountCodesFile' $!";
#
# PRINT THE HEADERS FOR THE COLUMNS OF DATA TO THE OUTPUT FILES
print $summaryFile "Discount_Code,Amount,# of uses,NewCustomers?,Min Order Amount,ExpiresOn\n";
my $i = 0;
for ($i; $i < $discountCount; $i++)
{
my %discountCodeInfo;
if($discountCode eq "")
{
# Generate a random code
$discountCode = rndStr 16, 0..9, 'a'..'z';
}
print "$i: DiscountCode = $discountCode\n";
if($limitToNewCustomers)
{
%discountCodeInfo = (discount=>{discount_type=>$discountType,code=>$discountCode,starts_at=>$discountStartDate,ends_at=>$discountEndDate,usage_limit=>$discountUsageLimit, applies_once_per_customer=>$appliesOnce,applies_to_resource=>"customer_saved_search",applies_to_id=>$newCustomerGroup,value=>$discountAmount,minimum_order_amount=>$minOrderAmount});
}
else
{
%discountCodeInfo = (discount=>{discount_type=>$discountType,code=>$discountCode,starts_at=>$discountStartDate,ends_at=>$discountEndDate,usage_limit=>$discountUsageLimit,applies_once_per_customer=>$appliesOnce,value=>$discountAmount,minimum_order_amount=>$minOrderAmount});
}
my $json = JSON->new;
my $discdata = $json->encode(\%discountCodeInfo);
# http://user:password@www.domain.com/script.pl
# CREATE THE REQUEST AND PASS IT TO SHOPIFY TO CREATE THE DISCOUNT CODE
my $URL = "https://$apiKey:$apiPass\@$storeUrl/admin/discounts.json";
my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 });
my $header = HTTP::Request->new('POST' => $URL);
my $request = HTTP::Request->new('POST', $URL, $header, $discdata);
$request->content_type('application/json');
# GET THE RESPONSE CODE BACK TO CHECK IF IT WAS SUCCESSFUL
my $response = $ua->request($request);
if ($response->is_success)
{
# PRINT THE DISCOUNT CODE INFO INTO THE LOG
if(defined $discountUsageLimit)
{
print $summaryFile "$discountCode, $discountAmount, $discountUsageLimit, $limitToNewCustomers, $minOrderAmount, $discountEndDate\n";
}
else
{
print $summaryFile "$discountCode, $discountAmount, , $limitToNewCustomers, $minOrderAmount, $discountEndDate\n";
}
my $arrayref = decode_json($response->content);
my $headerContent = $response->header('X-Shopify-Shop-Api-Call-Limit');
my @apiLimitBucket = split("/",$headerContent);
if($apiLimitBucket[0] > $apiLimitBucket[1] - 20)
{
usleep(18000000);
}
}
else
{
print "HTTP POST error code: ", $response->code, "\n";
print "HTTP POST error message: ", $response->message, "\n";
$i--;
}
$discountCode = "";
}
#
# CLOSE THE LOG FILES THAT THE SCRIPT WAS WRITING TO
close $summaryFile;
}
}
}
#################################################
#
# RUN THE FUNCTIONS DEFINED ABOVE
#
parse_args();
create_discount_codes();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment