Summary:
Becuase this application contains sensitive data, I cannot share a demo. Below, the table names have been omitted for security purposes. This application features Shopify, and Salesforce connectivity via their REST APIs. It also features loyalty tracking, Inventory tracking, and automated subscription tracking. This was one of my favorite projects because of its highly complex nature. Laravel is also one of the best PHP frameworks available. (The best in my opinion).
PHP
namespace App\Http\Controllers\Shopify;
use App\Models\Tier;
use App\Models\Order;
use App\Models\Dealer;
use Carbon\Carbon;
use DB;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Log;
use Mail;
use Illuminate\Mail\ExpirationNotification;
class ShopifyOrdersController extends Controller
{
public function delete( Request $request )
{
define( 'SHOPIFY_APP_SECRET', config( '###' ) );
function verify_webhook( $data, $hmac_header )
{
$calculated_hmac = base64_encode( hash_hmac( 'sha256', $data, SHOPIFY_APP_SECRET, true ) );
return ( $hmac_header == $calculated_hmac );
}
// Process the shopify call
$hmac_header = $_SERVER[ 'HTTP_X_SHOPIFY_HMAC_SHA256' ];
$data = file_get_contents( 'php://input' );
$verified = verify_webhook( $data, $hmac_header );
if( $verified )
{
$dataFile = json_decode( $data );
$orderId = DB::table( '###' )
->where( 'shopify_order_id', '=', $dataFile->id )
->select( 'id' )
->first();
if( count( $orderId ) > 0 )
{
$order = DB::table( '###' )
->where( 'shopify_order_id', '=', $dataFile->id )
->first();
DB::table( '###' )
->where( 'shopify_order_id', '=', $dataFile->id )
->update( [ "deleted_at" => date( "Y-m-d H:i:s") ] );
DB::table( '###' )
->where( 'order_id', '=', $order->id )
->update( [ "deleted_at" => date( "Y-m-d H:i:s") ] );
DB::table( '###' )
->where( 'shopify_id', '=', $order->dealer_id )
->decrement( 'accrued_points', $dataFile->subtotal_price );
} else
{
$data = 'There was no data found for this order. It never existed in the database';
}
header( 'Status: 200' );
} else
{
$data = '[error]: Verification Failed. Please the webhooks token should match the SHOPIFY_APP_SECRET. ---- ' . date( "m-d-Y_H-i-s" ) . '\nIf the problem persists the Webhook in Shopify will be deleted automatically';
}
}
public function orders( Request $request )
{
// Define the shopify secret variable
define( 'SHOPIFY_APP_SECRET', config( 'constants.shopify_app_secret' ) );
function verify_webhook( $data, $hmac_header )
{
$calculated_hmac = base64_encode( hash_hmac( 'sha256', $data, SHOPIFY_APP_SECRET, true ) );
return ( $hmac_header == $calculated_hmac );
}
// Process the shopify call
$hmac_header = $_SERVER[ 'HTTP_X_SHOPIFY_HMAC_SHA256' ];
$data = file_get_contents( 'php://input' );
$verified = verify_webhook( $data, $hmac_header );
if( $verified )
{
// Decode the json string to a json object
$dataFile = json_decode( $data );
// ORDER INFORMATION
// Order Variables
$orderId = $dataFile->id; // Order ID
$orderToken = $dataFile->token; // Order Token
$orderCartToken = $dataFile->cart_token; // Order Cart Token
$orderNumber = $dataFile->order_number; // Order Number
$orderCheckoutId = $dataFile->checkout_id; // Order Checkout ID
$orderStatusUrl = $dataFile->order_status_url; // Order Receipt URL
// DEALER VARIABLES
$dealerInfo = DB::table( '###' )
->where( 'shopify_id', '=', $dataFile->customer->id )
->select( 'shopify_id' )
->first();
$getPreviousId = DB::table( '###' )
->where( 'shopify_order_id', '=', $orderId )
->select( 'shopify_order_id' )
->get();
if( count( $getPreviousId ) > 0 ) {
header( 'Status: 200 ');
die();
} else {
// DEALER INFORMATION
// Dealer Variables
$shopify_id = $dataFile->customer->id; // Dealer ID
$dealerEmail = $dataFile->customer->email; // Dealer Email
// Save the orignal .json feed to a file
$public_path = public_path();
file_put_contents( $public_path . '/json_logs/' . $dealerInfo->shopify_id . "-" . $orderId . '.json', $data );
// Order Query
try {
$orderTableId = DB::table( '###' )
->insertGetId( array(
"dealer_id" => $dataFile->customer->id,
"receipt_url" => $orderStatusUrl,
"shopify_order_id" => $orderId,
) );
}
catch ( Exception $e ) {
}
// Order Items Array
$itemsArray = array();
foreach( $dataFile->line_items as $items_key => $items_value ) :
$itemsArray[ $items_key ][ 'order_id' ] = $orderTableId;
$itemsArray[ $items_key ][ 'item_id' ] = $items_value->id;
$itemsArray[ $items_key ][ 'variant_id' ] = $items_value->variant_id;
$itemsArray[ $items_key ][ 'quantity' ] = $items_value->quantity;
$itemsArray[ $items_key ][ 'order_points' ] = $items_value->price;
$itemsArray[ $items_key ][ 'item_sku' ] = $items_value->sku;
$itemsArray[ $items_key ][ 'product_id' ] = $items_value->product_id;
$itemsArray[ $items_key ][ 'item_name' ] = $items_value->name;
endforeach;
// insert order items
DB::table( '###' )
->insert( $itemsArray );
// CALCULATE TIER | POINTS | AFTER PURCHASE
$purchasePts = floatval( $dataFile->subtotal_price );
// CALCULATE TIER | POINTS | AFTER PURCHASE
$dealerLvl = DB::table( '###' )
->leftJoin( '###', '###.id', '=', '###.tier_id' )
->where( '###.shopify_id', '=', $dealerInfo->shopify_id )
->select( [
'###.tier_id',
DB::raw( 'SUM( ###.accrued_points + ' . $purchasePts . ' ) as tiers_total' )
] )
->first();
$dealer_level = floatval( $dealerLvl->tiers_total );
// DEALER TIER | NEW
$dealerNewLvl = DB::table( '###' )
->where( 'tiers.trigger', '<=', $dealer_level )
->select( [
'tiers.id',
'tiers.trigger',
] )
->orderBy( 'tiers.trigger', 'DESC' )
->first();
// Set the array for the dealer update
$dealerUpdate = array( 'dealers.accrued_points' => $dealer_level );
// Check if the 'tier' id changed
if( $dealerNewLvl->id != $dealerLvl->tier_id && $dealerNewLvl->id > $dealerLvl->tier_id )
{
// Add the new tier level tot he 'dealerUpdate' array
$dealerUpdate['dealers.tier_id'] = $dealerNewLvl->id;
}
// Update the 'dealers' record with the new 'accured_points' total
DB::table( '###' )
->where( '###.shopify_id', $dealerInfo->shopify_id )
->update( $dealerUpdate );
//CALCULATE THE ON HAND ITEMS PURCHASED FROM ORDER_ITEMS AND REGISTERED CUSTOMERS.
$dealerId = DB::table( '###' )
->where( 'shopify_id', '=', $dealerInfo->shopify_id )
->select( 'id' )
->first();
$customers = DB::table( '###' )
->where( '###.dealer_id', '=', $dealerId->id )
->count();
$ordersItems = DB::table( '###' )
->leftJoin( '###', 'orders.id', '=', '###.order_id' )
->leftJoin( '###', '###.shopify_id', '=', '###.dealer_id' )
->where( '###.id', '=', $dealerId->id )
->where( '###.item_sku', 'like', 'SU%' )
->sum( 'quantity' );
$reconciled = $ordersItems - $customers;
DB::table( '###' )
->where( 'dealers.id', '=', $dealerId->id )
->update( [
'on_hand' => $reconciled
] );
$dealer = Dealer::all()->where( 'id', '=', $dealerId->id )->first();
$shopify = new EndpointsController;
$call = $shopify->updateDealer( $dealer );
// SYNC SALESFORCE POINTS WITH APP POINTS.
if( $dealer->salesforce_id != NULL || $dealer->salesforce_id != '' )
{
//GET THE LAST TOKEN FROM THE DATABASE
$token = DB::table( '###' )
->select( 'token' )
->orderBy( 'id', 'desc' )
->first();
//SET QUERY TOKEN VARIABLE
$query_token = $token->token;
//SET UP HTTP HEADERS
$headers = array(
"Authorization: OAuth $query_token",
"Content-type: application/json"
);
//START THE CURL CALL
$curl = curl_init();
//PUT ARGS INTO AN ARRAY
curl_setopt_array(
$curl,
array(
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_URL => config( 'salesforce.credentials.loginURL' ) . "/services/oauth2/token", // URL FOR REFRESHING ACESS_TOKEN
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => http_build_query(
array(
'grant_type' => 'password',
'client_id' => config( '###' ),
'client_secret' => config( '###' ),
'username' => config( '###' ),
'password' => config( '###' ),
)
)
)
);
$plain_response = curl_exec( $curl );// GET RESPONSE
$response = json_decode( $plain_response ); // JSON DECODE RESPONSE IN THE CASE OF AN ERROR
// CLOSE THE CURL
curl_close( $curl );
//IF THE TOKEN ISN'T SET LOG THE ERROR IN LARAVEL.LOG
app( 'log' )->useDailyFiles( storage_path() . '/laravel.log' );
$access_token = ( isset( $response->access_token ) && $response->access_token != "" ) ? $response->access_token : app( 'log' )->critical( "line 274: access_token= was not set." . $plain_response );
//IF THE INSTANCE_URL ISN'T SET LOG THE ERROR IN LARAVEL.LOG
$instance_url = ( isset( $response->instance_url ) && $response->instance_url != "") ? $response->instance_url : app( 'log' )->critical( "line 275: instance_url was not set." . $plain_response );
//SET VARIABLES FOR QUERYING THE ACCOUNT INFORMATION
$instance_url = config( 'salesforce.credentials.loginURL');
$updateUrl = "$instance_url/services/data/v20.0/sobjects/Account/$dealer->salesforce_id";
$user = urlencode( config( 'salesforce.credentials.user' ) );
$pass = config( 'salesforce.credentials.pass' );
//SET THE FIELDS YOU WANT TO UPDATE IN SALESFORCE
$content = json_encode( array( "DEALER_POINTS__C" => $dealer_level ) );
//START CURL.
$curl = curl_init( $updateUrl );
//SET UP HEADER ARGUMENTS
curl_setopt( $curl, CURLOPT_HEADER, false );
curl_setopt( $curl, CURLOPT_HTTPHEADER,
array("Authorization: OAuth $access_token",
"Content-type: application/json"));
//PATCH CUSTOMREQUEST TO UPDATE DEALER POINTS IN SALESFORCE
curl_setopt( $curl, CURLOPT_CUSTOMREQUEST, "PATCH" );
curl_setopt( $curl, CURLOPT_POSTFIELDS, $content );
// CAPTURE SALESFORCE RESPONSE
$response = curl_exec( $curl );
// CAPTURE HTTP STATUS CODE
$status = curl_getinfo( $curl, CURLINFO_HTTP_CODE );
// IF NOT SUCCESSFUL LOG THE ERROR
if( $status != 204 )
{
app( 'log' )->useDailyFiles( storage_path() . '/laravel.log' );
app( 'log' )->critical( "line 305 | Error: call to URL $instance_url failed with status $status, curl_error " . curl_error( $curl ) . ", curl_errno " . curl_errno( $curl ) );
//SEND AN EMAIL TO THE ADMIN
\Mail::raw( 'Salesforce Connected App Failed check laravel.log and ShopifyOrdersController for errors', function( $message )
{
$message->to( config( 'constants.admin_email' ) );
$message->subject( 'Salesforce Connected App Error' );
} );
}
//CLOSE CURL
curl_close( $curl );
}
// ENABLED QUERY LOG TO WATCH WHICH QUERIES ARE BEING MADE
DB::enableQueryLog();
//SHOW SHOPIFY RETURN STATUS OF 200
header( 'Status: 200' );
}
} else // IF SHOPIFY WEBHOOK DOESN'T VERIFY
{
//CRITICAL ERRORING
app( 'log' )->critical( '[error]: Verification Failed. Please the webhooks token should match the SHOPIFY_APP_SECRET. ---- ' . date( "m-d-Y_H-i-s" ) . '\nIf the problem persists the Webhook in Shopify will be deleted automatically.' );
// ALERT TO ADMIN
\Mail::raw( 'Shopify Verifiation Failed check SPEQ ShopifyOrdersController for errors', function( $message )
{
$message->to( config( 'constants.admin_email' ) );
$message->subject( 'Shopify Verification Error' );
} );
//RETURN 200 TO PREVENT SHOPIFY DELETING THE WEBHOOK
header( 'Status: 200' );
}
}
}