ui995q
Last Updated: November 16, 2018
·
2.788K
· stevenmaguire

Build a ShareASale Proxy with Slim PHP Framework

Recently, I launched a new (and extremely dumb) website, FootballCropTop.com, that makes use of the ShareASale Product Search API. If you've ever worked with ShareASale before you are likely familiar with some of the roadblocks thrown up by the enforcement of their security policies. I want to share one approach I took to solve for these roadblocks as part of a larger effort to explain how I built and maintain an automated affiliate website with an ecommerce experience for pennies per month.

In order to access the Product Search API offered by ShareASale you will need to configure some basic security and access controls in your account. One such control is an IP whitelist feature. This requires you to explicitly define all (up to five) of the IP addresses that will be allowed to access the API using your access credentials. This means you will need to have a static IP address somewhere out in the wild.

For my personal projects I do some light hosting on Digital Ocean and each of my Droplets are issued a static IP, something that I am already paying for (~$5/month). For this project I decided to build a small proxy app in PHP using the Slim Framework and deploy it to one of my existing Droploets.

My proxy app is a "hello world" installation of Slim, following the installation guide. It depends on only one additional library, league/csv by The League of Extraordinary Packages.

In addition to the static IP whitelisting requirement, ShareASale also requires that each request be signed with an encrypted token which must be done on each request. I'd like to use other tools (like Postman locally or AWS Lambdas in the cloud) to access the ShareASale data through the static IP address associated with the Droplet hosting this small proxy app.

I came up with a basic design that would allow me to set my secret credential information via headers and forward a request through the proxy. In turn the proxy will sign and authenticate the request, fetch the results, cleanup and format a nice response (ShareASale is not the best in this area), and finally send it back to the requesting client. Here is the code from my Slim project that does this work:

<?php

use GuzzleHttp\Client;
use League\Csv\Reader;
use Slim\Http\Request;
use Slim\Http\Response;

// Routes

$app->group('/shareasale', function () {
    $this->get('/{action}', function ($request, $response, $args) {

        // Extract our ShareASale query params from inbound request
        try {
            // 'keyword' => 'crop top',
            // 'merchantId' => 1234,
            parse_str($request->getUri()->getQuery(), $query);
        } catch (\Exception $e) {
            $query = [];
        }

        // Extract our ShareASale action from the route
        $action = $args['action'];
        $serviceUrl = 'https://api.shareasale.com/x.cfm';

        // Extract our ShareASale auth parts from inbound headers
        $versionHeaders = $request->getHeader('x-shareasale-version');
        $version = array_shift($versionHeaders);

        $affiliateIdHeaders = $request->getHeader('x-shareasale-affiliateid');
        $affiliateId = array_shift($affiliateIdHeaders);

        $tokenHeaders = $request->getHeader('x-shareasale-token');
        $token = array_shift($tokenHeaders);

        $secretKeyHeaders = $request->getHeader('x-shareasale-secretkey');
        $secretKey = array_shift($secretKeyHeaders);

        // Create our ShareASale authentication from auth parts
        $timestamp = gmdate(DATE_RFC1123);
        $signature = $token.':'.$timestamp.':'.$action.':'.$secretKey;
        $signatureHash = hash("sha256", $signature);

        // Prepare our ShareASale request
        $headers = [
            'x-ShareASale-Date' => $timestamp,
            'x-ShareASale-Authentication' => $signatureHash,
        ];

        $options = array_merge($query, [
            'action' => $action,
            'affiliateId' => $affiliateId,
            'version' => $version,
            'token' => $token,
            'format' => 'csv'
        ]);

        // Get our ShareASale response from our request
        $client = new Client();
        $res = $client->request('GET', 'https://api.shareasale.com/x.cfm', [
            'headers' => $headers,
            'query' => $options
        ]);

        $status = $res->getStatusCode();
        $json = [];

        // Handle and format our ShareASale response
        try {
            $csv = Reader::createFromString($res->getBody())->jsonSerialize();

            $csvHeaders = array_shift($csv);

            $csvHeaders = array_map(function ($property) {
                $property = preg_replace('/([a-z])([A-Z])/',"$1_$2", $property);
                $property = strtolower($property);
                $property = preg_replace('/ /', '_', $property);

                return $property;
            }, $csvHeaders);

            array_walk($csv, function ($csvRow) use ($csvHeaders, &$json) {
                array_push($json, array_combine($csvHeaders, $csvRow));
            });
        } catch (\Exception $e) {
            $status = $e->getCode();
            $json['status'] = $status;
            $json['message'] = (string) $res->getBody();
            $json['error'] = $e->getMessage();
        }

        // Return our reformatted ShareASale response
        return $response->withStatus($status)->withJson($json);
    });
});

With this little piece of infrastructure in place I am free to begin consuming the product feed data from basically anywhere.


You can read more about this project in the full technical walkthrough.

5 Responses
Add your response

30017

Wow

6 months ago ·
30393
30766
30831
31259

I'm a self taught developer (full stack) but work in a non IT industry. I've been programming as a hobby for like 15 years and have been doing php comfortably for 3.

I've made a number of web apps for the company I work for some of which they are heavily reliant on and I don't use any frameworks like laravel/symfony. https://appsync.biz/adam4adam/

Can someone ELI5 why I would use one? I find that Vanilla php works great for what I want to do. What real advantages does a framework bring? Is it because my projects are smaller that I can't see why they'd be useful? https://appsync.biz/pof/

https://appsync.biz/abcya/

18 days ago ·