Friday, June 16, 2017

Setting up the Printful API communication with Python

I recently set up a little shop on my website (www.benty-fields.com/benty-shop) using Printful.com. Here I will explain how to do this using Python.

If you want to run an online business using printing services like Printful, by far the easiest approach is to use Shopify or any other online business solution. However, I just wanted to attach a small shop to my own website. For such a case Printful provides an API, which however requires a little bit of work.

I am running my app using Flask in combination with a Postgres database behind a SQL alchemy ORM. First I selected the products I wanted to sell and store the products in my database. I will not discuss the details of my database setup here, but the easiest way for you would be to copy the Printful product database layout.

I then built an HTML/Javascript frontend for my store, which you are welcome to visit at www.benty-fields.com/benty-shop. Whenever a customer selects a product, I create an order object in my database, which however requires the variant_id from the Printful API. A simple solution would be to just download the entire product and variant list from Printful. This has the advantage that you would not need to make an API call to get the variant_id and hence can provide quicker response to any customer action. I, however, opted to write a small function, which queries the Printful API and returns the variant id. This way I am more flexible against any changes in the Printful variant_id list.

Here is the function I use:
try:
    response = requests.get(url)
except requests.exceptions.RequestException as e:
    print("ERROR: %s" % str(e))
    return False
if response.status_code == 200:
    data = json.loads(response.text)
    # We count the number of positive variants we find,  
    # just to be sure that our variant selection is unique
    count = 0
    # Find the variant specified by the color and size of the order
    for variant in data['result']['variants']:
        # First we select the size (if size is not null). 
        # Size is null if the product comes in only one size
        if variant['size'] == 'null':
            # There is no case I know of where size and color are null
            if variant['color'] == order.color:
                variant_id = variant['id']
                count += 1
        elif variant['size'] == order.size:
            # Second we select the color (if color is not null).  
            # Color is null if the product comes in only one color
            if variant['color'] == 'null':
                variant_id = variant['id']
                count += 1
            elif variant['color'] == order.color:
                variant_id = variant['id']
                count += 1
    # to make absolutely sure that color and size define a variant for this product
    if count == 0:
        print("ERROR: No variant found with this size %s and color %s "
              for product id %d" % (order.size, order.color, 
                                    order.product.printful_product_id)
        return False
    elif count > 1:
        print("ERROR: Size and color are not enough to define the variant "
              for product id %d" % order.product.printful_product_id
        return False
    else:
        return variant_id
else:
    print("ERROR: Printful api call unsuccessful, code = %d,"
          "message = %s" % (response.status_code, response.text)
    return False
This code uses the size and color to find the variant id. This assumes that a set of size and color is enough to determine the variant_id, and I have not come across any Printful product where there is a third dimension. The code, however, makes sure that only one variant_id satisfies the specification otherwise it will return False.

Having set the variant id it is quite easy to submit a Printful order after the customer provided the payment details. Here is my function for that:

def create_printful_order(order):
    ''' This function submits a printful order '''
    order_json = {
        "recipient": {
            "name": order.shipping_name,
            "address1": order.shipping_address,
            "city": order.shipping_city,
            "state_code": order.shipping_state_code,
            "country_code": order.shipping_country_code,
            "zip": order.shipping_zip_code
        },
        "items": [{}]
    }
    order_json['retail_costs'] = { "shipping": order.shipping_cost }
    items = []
    # Process each item in the order and attach them to the json object
    for order_item in order.items:
        item = {
            "variant_id": order_item.variant_id,
            "quantity": order_item.quantity,
            "name": order_item.product.title,
            "retail_price": order_item.product.price,
            "files": [{
                "id": order_item.print_file_id
            }]
        }
        items.append(item)
    order_json['items'] = items
    url = app.config['PRINTFUL_API_BASE'] + 'orders'
    b64Val = base64.b64encode(app.config['PRINTFUL_API_KEY'])
    headers = {'content-type': 'application/json',
               'Authorization': "Basic %s" % b64Val}
    try:
        response = requests.post(url, data=json.dumps(order_json),
                                 headers=headers)
        print("response = ", response.status_code, response.text)
        return True, response
    except requests.exceptions.RequestException as e:  
        print("ERROR: When submitting order with requests, "
              "error message: %s" % str(e))
        return False, e
Note that you have to 64-bit encode your API key, otherwise the Printful API will not understand your request. It should be quite easy to replace my order_item object with whatever database setup you are using.

I posted the code on GitHub. Let me know if you have any comments.
cheers
Florian

No comments:

Post a Comment