Python — Shopify Dynamic Discount Code Creation With Urllib2

The Shopify API has no support for discount code creation or mass discount code uploads by other means. Still, I needed a way to create 1 discount code for every order that comes in.

I was surprised to solve this rather painlessly in a short time since I’ve never used anything with urllib2 or parsing. I will document the final result instead of my trials and errors.

I like that this solution uses only basic functions. You don’t learn if it’s magic. I saw an example using mechanize that was only 20 odd lines of codes, but I don’t follow it / it’s not in python.

Next time I need to do this, I will look into python mechanize.

Here are the main steps involved in sending web requests to Shopify:

Authentication

First, we need to log in to Shopify programmatically — not through their API.

Use the urllib2 library to do so.

First, set up urllib2 to use the “opener” urllib2.HTTPCookieProcessor, which will handle cookie handling for your session.

import urllib, urllib2

opener = urllib2.build_opener( urllib2.HTTPCookieProcessor() )
urllib2.install_opener( opener )

# future requests will use the cookie processor that we install here

The next step is to log in to Shopify by setting up our urlencoded post parameters for this specific login form. All i needed to do was check out the login form and find the field names they are expecting: login, and password.

Let’s give it a shot:

params = { 'login': 'myusername', 'password': 'mypassword' }
encoded_params = urllib.urlencode( params )

_file = opener.open( 'https://myshop.myshopify.com/admin/auth/login', encoded_params )
response = _file.read()
_file.close()

# success!

Now, due to the Cookie handler, we can simply open new admin url pages and they will load without fuss.

_file = o.open('http://myshop.myshopify.com/admin/marketing')
_file.read()
_file.close()

#Works!

next step…

Posting to the discount code form…

First, navigate to the discount code page to see how their magic is happening. myshop.myshopify.com/admin/marketing is where you need to be.

I use google chrome, so you can open the developer tools, go to the resources tab, and click on the XHR subtab to see what AJAX requests are being performed when you create a discount code.

Here, I found all the information I needed to POST to replicate this behavior.

What exact headers if any that Shopify needs to see, what exact post parameters if any that shopify needs.

The important piece I see here is that there is an authentication token as a security measure Shopify takes.
We’ll need to hunt down this token and store it before we move on to making our POST.

Hunting down the authentication token

First, we need to load the /admin/marketing page to find this token. I used beautiful soup to parse the html and find the token.

        _file = opener.open( 'http://myshop.myshopify.com/admin/marketing' )
        html = _file.read()
        _file.close()

        soup = BeautifulSoup(html)
        auth_token = soup.find('input', type='hidden', attrs={'name': 'authenticity_token'})
        authentication_token = auth_token['value']
        # yay! our auth token found.

Setting up the request headers so Shopify doesn’t complain

With the auth token found, we need to modify our post headers or else Shopify will give you a 406 error. I didn’t bother to check which exact headers they check for, so I copied all of the headers I found in the screenshot above:

        post_headers = {
            'Accept': 'text/javascript, text/html, application/xml, text/xml, */*',
            'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'User-Agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.41 Safari/534.7',
            'X-Prototype-Version': '1.7_rc2',
            'X-Requested-With': 'XMLHttpRequest',
        }

        # note, addheaders takes a list of tuples
        headers = [(x, y) for x, y in post_headers.iteritems()]
        opener.addheaders = headers

Setting up our actual POST

Our headers are ready, we have our authentication token, now lets set up our post data.

As in the screenshot, we know what fields Shopify is expecting. I just copied them all.

        form_data = {
            'utf8': '✓',
            'authenticity_token': authentication_token,
            'discount': 'helloDiscountCodeFromPython',
            'discount[value]': '10',
            'type':'fixed_amount',
            'discount[starts_at]': '',
            'discount[ends_at]': '',
            'discount[minimum_order_amount]': '0.00',
            'discount[usage_limit]': '1',
            'commit': 'Create Discount',
            'page': '1',
            '_': '',
        }

Let's make a discount code!

Finally, we're ready.

Here's the completed list of what we need:

  • Authentication. We're logged in.
  • The Authentication token.
  • The POST headers for AJAX / whatever Shopify is expecting
  • The POST itself

So let's give this a shot!

encoded_post_data = urllib.urlencode( form_data )
_file = opener.open( 'https://myshop.myshopify.com/admin/discounts', encoded_post_data)
response = _file.read()
_file.close()

# it works!

Fantastic! It works! I've taken this and built a class that will take options for the various discount code fields and check whether the response was successful or not (very crudely), etc.

Here's the whole process

I'll copy and paste the pieces here into one block

import urllib, urllib2

opener = urllib2.build_opener( urllib2.HTTPCookieProcessor() )
urllib2.install_opener( opener )
# future requests will use the cookie processor that we install here

# log in

params = { 'login': 'myusername', 'password': 'mypassword' }
encoded_params = urllib.urlencode( params )

_file = opener.open( 'https://myshop.myshopify.com/admin/auth/login', encoded_params )
response = _file.read()
_file.close()

# success!

# parse for auth token

_file = opener.open( 'http://myshop.myshopify.com/admin/marketing' )
html = _file.read()
_file.close()

soup = BeautifulSoup(html)
auth_token = soup.find('input', type='hidden', attrs={'name': 'authenticity_token'})
authentication_token = auth_token['value']

# auth token found.

# set up post headers

post_headers = {
    'Accept': 'text/javascript, text/html, application/xml, text/xml, */*',
    'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'User-Agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.41 Safari/534.7',
    'X-Prototype-Version': '1.7_rc2',
    'X-Requested-With': 'XMLHttpRequest',
}

# note, addheaders takes a list of tuples
headers = [(x, y) for x, y in post_headers.iteritems()]
opener.addheaders = headers

# done adding headers to our opener (which means it will be used every time from now on)

# set up our post

form_data = {
    'utf8': '✓',
    'authenticity_token': authentication_token,
    'discount': 'helloDiscountCodeFromPython',
    'discount[value]': '10',
    'type':'fixed_amount',
    'discount[starts_at]': '',
    'discount[ends_at]': '',
    'discount[minimum_order_amount]': '0.00',
    'discount[usage_limit]': '1',
    'commit': 'Create Discount',
    'page': '1',
    '_': '',
}

encoded_post_data = urllib.urlencode( form_data )
_file = opener.open( 'https://myshop.myshopify.com/admin/discounts', encoded_post_data)
response = _file.read()
_file.close()

# discount code created!

4 thoughts on “Python — Shopify Dynamic Discount Code Creation With Urllib2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s