## Array Rotation in PHP

An operation to rotate an array will shift each element to the left. We can perform this function X number of times depending on the input parameters. Since the lowest index element moves to the highest index upon rotation, this is called a circular array.

All we need to do is remove the first element of the array, save it in a variable, and then attach it to the end of the array. This can be done inside of a loop that iterates the number of times that we want to rotate the data. The function can take in the original array and the number of rotations. It returns the newly rotated array:

```function rotateLeft(\$array, \$rotations) {
for(\$i=0;\$i<\$rotations;\$i++){
\$firstElement = array_shift(\$array);
array_push(\$array, \$firstElement);
}
return \$array;
}
```

Although this code will work, and gets the job done, it is slow. When I tried this as a solution on HackRank (a code challenge website) it passed 9/10 test cases. It failed once, citing “time limit exceeded”.

The problem is with array_shift. It’s a PHP function that removes “the first value of an array off and returns it.” This shortens the array by a single element, moving everything else down one index. The algorithmic efficiency (expressed in Big-O notation) of array_shift() is O(n). That means, the larger the input array, the more time it will take to complete.

Next, I tried using array_reverse and array_pop. I figured that since array_pop() has a constant algorithmic efficiency, noted as O(1), it would help. Regardless of the size of the input, it would always take the same amount of time.  But, due to the use of array_reverse (twice!) it was even slower:

```function rotateLeft(\$array, \$rotations) {
for(\$i=0;\$i<\$rotations;\$i++){
\$array = array_reverse(\$array);
\$firstElement = array_pop(\$array);
\$array = array_reverse(\$array);
array_push(\$array, \$firstElement);
}
return \$array;
}
```

Finally, I found a solution that was performant:

```function rotLeft(\$array, \$rotations) {
\$remaining = array_slice(\$array, \$rotations);
array_splice(\$array, \$rotations);
return array_merge(\$remaining, \$array);
}
```

This code does not need to use any kind of loop, which helps to speed things up. First, array_slice() grabs the elements up to the point that the data should be rotated, and saves them to the variable \$remaining. Next, array_splice() removes those same elements. Lastly, array_merge() glues the elements back together in the expected, rotated order.

This sort of computer science programming challenge can commonly be found during software engineering job interviews. When coming up with an answer, it is important to consider speed and performance. Understanding computing cost-effectiveness and Big-O can be a deciding factor in how your coding skills are judged.

## Look-and-say in PHP

The look-and-say sequence is a series of integers. It can grow indefinitely. It is generated by reciting a number phonetically, and writing what you spoke numerically. Its popularity is attributed to famed cryptographer Robert Morris. It was introduced by mathematician John Conway. It looks like this:

```1
11
21
1211
111221
312211
13112221
```

The first line would be pronounced as “one 1”, and then written as “11” on the second line. That record would be spoken as “two 1’s”,  giving us the third line “21”. The greatest individual symbol you’ll ever find in this consecution is a 3.

This topic has lots of trivia, variations, and history that could be dug up and expounded upon. Here, I’ll explain a solution written in PHP to produce this chain of numerals. The input will be the count of how many lines, or iterations, in the series to generate. Below is the code:

```<?php

echo "Count And Say: \n";

function countAndSay(\$count=0){
\$value = 1; // initial seed
for(\$i=1;\$i<=\$count;\$i++){
echo \$value . "\n";
\$value = calcOutput(\$value);
}
}
function calcOutput(\$value){
\$value = "\$value";  // change it into a string, so we can iterate over each character
\$current = \$value[0]; // first character
\$count = 1;
\$return = '';
for (\$i = 1; \$i <= strlen(\$value); \$i++) { // keep going until we get through the whole string
if (\$current != \$value[\$i] || \$i == strlen(\$value)) { // found a different character, or end of the input string
\$return .= "\$count\$current";
\$count = 1; // reset count
\$current = \$value[\$i]; // set new current character
} else {
\$count++;
}
}
return \$return;
}

countAndSay(7);

echo "\n\n";

?>
```

I separated my code into two functions. I think this is the best approach. As an exercise, see if you can figure out how to refactor it into one. This could help you to internalize the logic as you write it out for yourself.

The initial seed value is “1”, and that is hard-coded at the top. The for-loop iterates based on the count input parameter. That means the code circles back and re-runs, with updated values, until its internal count (represented by the variable \$i ) matches the \$count variable passed into countAndSay(\$count).

The code that we loop over outputs the current sequence value (starting with 1) as its own line (“\n” will output a new line in most programming languages) , and then calculates the next. The function that determines the next line of output, calcOutput(\$value), takes the current value as an argument.

The first thing we do is cast the integer value passed along into a string. This lets us refer to each character by index – starting at zero – and save it to a variable \$current. We start a new \$count, to keep track of how many times we see the same digit.

The next for-loop executes for the length of the \$value string. On each loop, we check if the \$current character we saved matches the subsequent one in that \$value string. It is again referenced by index, this time based on the for-loop’s iteration count represented by the variable \$i.

If it does match, one is added to the \$count variable that is keeping track of how many times we see the same character is a row. If it doesn’t match (or we’ve reached the end of the input), the \$count and \$current number are concatenated to the \$return element. At that point, the \$count is reset to 1, and the \$current value is updated.

Writing an algorithm to generate the look-and-say (also known as, count-and-say) sequence is a common coding puzzle. You might run into it during a job interview as a software engineer. As practice, see if you can simplify my example code, or even write it in a different programming language than PHP.

## CSS for weighted hyperlink decoration

How to add an underline to website text should be covered in any intro to web development course. The old-fashioned HTML way uses a now-deprecated tag:

`<u>This will appear underlined!</u>`

Modern approaches use CSS to define such a style:

`<p style="text-decoration: underline;">This will appear underlined!</p>`

Even better, properly written code will separate the inline styles, like so:

```<style>
.underlined-text{
text-decoration: underline;
}
</style>
<p class="underlined-text">This will appear underlined!</p>```

For hyperlink text, I might want to hide the underline when a user mouses over it. That’s easy with the “hover” pseudo-class:

```<style>
text-decoration: underline;
}
text-decoration: none;
}
</style>
```

But, suppose I want to have that underline to become thicker instead of disappearing. That will require an advanced, super-secret CSS technique. To make it work, we will utilize box-shadow.

In the world of cascading style sheets, the box-shadow property adds a shadow effect around any HTML element. The shadow is described by its offsets, relative to the element. Leveraging this, we can create a shadow that looks like an underline. On hover, we can adjust the settings to change its appearance:

```<style>
text-decoration: none;
box-shadow: inset 0 -1px 0 rgb(15 15 15);
}
-webkit-box-shadow: inset 0 0 0 rgb(0 0 0 / 0%), 0 3px 0 rgb(0 0 0);
box-shadow: inset 0 0 0 rgb(0 0 0 / 0%), 0 3px 0 rgb(0 0 0);
}
</style>
```

Point your cursor over any of the hyperlinks in this post to see what it looks like. Experiment with the above code to make it your own.

## The Great Resignation

The Great Resignation is an early 2020s buzz phrase. It describes a large portion of the workforce quitting. This Big Quit has been precipitated by numerous factors, but most obviously the CoVid-19 pandemic. Truthfully, the snowball started rolling prior to the worldwide shutdown. Online freelancing and digital nomadism have been picking up steam for nearly a decade. Ubiquitous Wi-Fi, inexpensive devices, and on-demand cloud computing paved the road of the Extraordinary Exodus. The ability to work remotely, I believe, is one of the strongest drivers of this cultural shift.

Employment as we know it is a raw deal. It exploits workers and business owners. Only since the advent of the industrial revolution has the modern work schedule and environment taken this shape.

Employees are tasked to toil in serfdom. The employer is expected to be a parent. Those costs, risks, and responsibilities weigh heavily on executive shoulders. And that gravity ultimately crushes workers as exploitation.

Employer sponsored medicine took hold after World War 2. To fight inflation, the 1942 Stabilization Act was passed. That law prevented employers’ from raising wages. As a work-around to compete for high-demand workers, companies began offering health benefits as a competitive incentive. Nearly a century later, healthcare and employment are intertwined in the bleakest of scenarios.

Now can be the epoch of freedom. It’s the original promise of all the technological advances we now enjoy. Greater prosperity, with fewer shackles to hold us down. That also means businesses can enjoy larger output with less overhead.

When writing this, I was careful not to refer to employers as bosses. Ultimately, they are the clients. Workers should be expected to “manage up”, as they control labor, and in turn, production output and capital income.

It’s an absurd burden that businesses are expected to provide employees with healthcare. With that type of expectation, employers feel justified squeezing out every last drop of labor juice – be it sweat or tears.

Even worse, as workers, it’s crazy that access to medicine is tied to employment. Public health should be a public service, paid for by taxes. And, I’m not suggesting that taxes should be raised, but instead reallocated from bureaucratic waste, corruption, and cruelty.

Healthcare is among numerous benefits subsidized and off-loaded to the private sector. It has a taste similar to the Western tipping scheme found in the restaurant service industry.

## Freedom

Nine hours in an office each day doesn’t leave time for much else. It was a routine I faced for years, along with many other New York straphangers. A long daily commute was the little time I could find to read, meditate, or relax. My only exercise came from pushing deadlines, jumping through hoops, and squeezing in meetings.

An age of abundance is possible within our lifetime. Laborers are being replaced by technology. Remember, most jobs are bullshit anyway. Humanity can feed itself, and anything more is just pollution.

## Restoring a website when EC2 fails

Recently, one of my websites went down. After noticing, I checked my EC2 dashboard and saw the instance stopped. AWS had emailed me to say that due to a physical hardware issue, it was terminated. When an instance is terminated, all of its data is lost. Luckily, all of my data is backed up automatically every night.

Since I don’t use RDS, I have to manually manage data redundancy. After a few disasters, I came up with a solution to handle it. I trigger a nightly cron-job to run a shell script. That script takes a MySQL dump and uploads it to S3.

As long as I have the user generated data, everything else is replaceable.  The website that went down is a fitness tracking app. Every day users record their martial arts progress. Below are the ten steps taken to bring everything back up.

1. Launch a new EC2 instance
2. Configure the security group for that instance –  I can just use the existing one
4. Secure the database
5. Install PhpMyAdmin – I use this tool to import the .sql file in the next step
6. Import the database backup – I downloaded the nightly .sql file dump from my S3 repo
7. Setup automatic backups, again
8. Install WordPress and restore the site’s blog
9. Configure Route 53 (domain name) and SSL (https) – make the website live again
10. Quality Assurance – “smoke test” everything to make sure it all looks correct

Use this as a checklist to make sure you don’t forget any steps. Read through the blog posts that I hyperlinked to get more details.

## Shopify App with Theme App Extensions

After writing my last post about How to create a Shopify app, I decided to build a new one as a side project. Taking myself through the entire process helped me to tighten up the details I mentioned. This one adds a sticky banner to a store’s front-end, prompting users to “click to call”.

It’s built on top of the code I used for the SplitWit Shopify app. I adjusted some of the methods to accept configuration parameters to differentiate between the two. Code had to be added to support new functionality. SplitWit already had a feature to add a sticky banner to a site’s existing UI. I used the same workflow to inject the merchant’s settings as JavaScript.

```function writeSnippetFile(\$shopify_installation_complete_id){

\$conn = \$this->connection;
\$sql = "SELECT * FROM `prompts` WHERE shopify_installation_complete_id = ?";
\$result = \$conn->prepare(\$sql);
\$result->execute(array(\$shopify_installation_complete_id));
\$row = \$result->fetch(PDO::FETCH_ASSOC);
\$phone_number = \$row['phone_number'];
\$message_text = \$row['message_text'];
\$color = \$row['color'];
\$bg_color = \$row['bg_color'];
\$position = \$row['position'];
\$display = \$row['display'];
\$mobile_only = \$row['mobile_only'];
\$filename = \$row['snippet'];

\$sticky_html = "";

if(\$display == "hide"){
\$sticky_html .= "<style>#ctc-splitwit-sticky{display:none;}</style>";
}

if(\$mobile_only == "true"){
\$sticky_html .= "<style>@media(min-width: 1000px){#ctc-splitwit-sticky{display:none;}}</style>";
}

\$sticky_html .= "<div style='font-weight:bold;".\$position.":0;position:fixed;z-index:100000;left:0px;text-align:center;padding:8px 20px;width:100%;background:".\$bg_color.";color:".\$color."' id='ctc-splitwit-sticky'><p style='margin:0px'><a href='tel:".\$phone_number."'>".\$message_text."</a></p></div>";

\$changecode = '\$("body").append("'.\$sticky_html.'")';

\$snippet_template = file_get_contents("/var/www/html/click-to-call/snippet/snippet-template.js");
\$myfile = fopen("/var/www/html/click-to-call/snippet/".\$filename, "w") or die("Unable to open file!");

\$txt = "window.CTCsplitWitChangeCode = ".\$changecode . "\n" . \$snippet_template;

fwrite(\$myfile, \$txt) or die("Unable to save file!");

fclose(\$myfile);
}
```

The app’s admin view is a simple input form with settings to control the sticky bar UI that is injected into the merchant’s store-front.

In addition to updating and refactoring my code, I wrote copy and drafted design for this digital product. I used SplitWit branding guidelines (fonts, colors, etc.) to establish an adjacent feel.

Although it’s optional, I wanted to include a promo video in the listing. Having had previously created videos for SplitWit, I was able to quickly spin one together. I already created background music files in Garage Band for other projects. Here’s the one I chose to use – feel free to borrow it for what ever you like. The text animations were exported from Keynote. I added screenshots, included stock animation from VideoPlasty, and recorded voice-over lines using a Yeti microphone.

I drafted other graphic assets that were required in the app listing using the GIMP – software I’ve used for over twenty years

A few days after submission, I received an email with required changes.

They were mostly minor issues. Things like the app’s name, a screenshot used in the listing, and an OAuth redirect bug.

One of the requests said, “Update your app to theme app extensions to ensure compatibility with Online Store 2.0 themes.”

### 2.0 themes? What does that mean?

Shopify recently announced Online Store 2.0 (OS 2.0). It’s essentially a set of improvements to the platform that makes themes and apps more flexible and maintainable. This benefits both merchants and developers. Enhanced app support means app functionality can be leveraged anywhere in a theme by using app blocks in the theme customizer.

SplitWit Click-to-Call injects HTML to manipulate a store’s user interface. That code comes from a JavaScript file that’s referenced in the page’s source code. That reference is added upon installation using the ScriptTag API. The JS file itself is generated & updated whenever a merchant clicks “save” in the app’s admin view. This required change is requesting that we provide an app block option as an alternative for compatible themes.

Shopify is encouraging OS 2.0 apps to instead use “theme app extensions” because they don’t edit theme code. It allows merchants to add your app’s UI elements, along with any settings, through its theme editor.

The documentation mentions that it “reduce[s] the effort required to integrate apps in themes”. In my particular case, it actually seems to add a step.

## Theme App Extensions

App blocks are a type of “theme app extension” supported by Shopify’s Online Store 2.0. To create an app block available in the theme editor, I added a directory to my app using the below file structure. I was able to auto-generate it with the Shopify CLI by using the command shopify extension create.

From the command line, I registered this folder as an extension, and pushed my code.

In my app block .liquid file I used the same HTML template from my original PHP snippet, swapping my database variables for Shopify block settings.

```{% if block.settings.display == "hide" %}
<style>#ctc-splitwit-sticky{display:none;}</style>
{% endif %}

{% if block.settings.mobile_only == "yes" %}
<style>@media(min-width: 1000px){#ctc-splitwit-sticky{display:none;}}</style>
{% endif %}

<div style="font-weight:bold;{{block.settings.position}}:0;position:fixed;z-index:100000;left:0px;text-align:center;padding:8px;width:100%;background:{{block.settings.bg_color}};color:{{block.settings.color}}" id="ctc-splitwit-sticky"><p style=margin:0px><a style="color:{{block.settings.color}} " href="tel:{{block.settings.phone_number}}">{{block.settings.message_text}}</a></p></div>

{% schema %}
{
"name": "Click To Call",
"target": "section",
"settings": [
{
"type": "color",
"id": "bg_color",
"label": "Banner color",
"default": "#0000FF"
},
{
"type": "color",
"id": "color",
"label": "Text color",
"default": "#FFFFFF"
},
{
"type": "text",
"id": "message_text",
"label": "Message text",
"default": "Call us now!"
},
{
"type": "text",
"id": "phone_number",
"label": "Phone number",
"default": "(212)-555-5555"
},
{
"id": "position",
"label": "Position",
"options": [
{
"value": "top",
"label": "Top"
},
{
"value": "bottom",
"label": "Bottom"
}
],
"default": "top"
},
{
"id": "display",
"label": "Display",
"options": [
{
"value": "hide",
"label": "Hide"
},
{
"value": "show",
"label": "Show"
}
],
"default": "show"
},
{
"id": "mobile_only",
"label": "Mobile only",
"options": [
{
"value": "yes",
"label": "Yes"
},
{
"value": "no",
"label": "No"
}
],
"default": "no"
}
]
}
{% endschema %}
```

The schema JSON explicates the settings inputs exposed to the merchant. I set them to match the original settings view from my app’s interface.

This approach lets Shopify maintain the app’s configurations, instead of my SplitWit server database that’s hosted on AWS EC2. That’s less data developers can capture, but also less of a hosting burden.

Any time you update the theme app extension you’ll need to re-push the code from the Shopify CLI with the command shopify extension push. The extension code is not hosted on your own server. It lives solely in the Shopify infrastructure ecosystem.

### Verify theme support

Not all themes support theme app extensions. Theme support needs to be verified at the time of installation.

The original settings view is still needed, just in case the merchant’s published theme does not support app blocks. If app blocks are supported, I don’t install the script tag snippet at all. Instead, the settings view is replaced with integration instructions telling the merchant how to activate the sticky banner from the theme editor.

If app blocks are not supported by the active theme, the snippet is installed and the settings input form displayed. Determining if the merchant’s theme supports app blocks requires adding the read_themes Shopify API scope access to the oAuth request.

```\$scopes = "write_script_tags,read_themes";
\$redirect_url = "https://".\$shop."/admin/oauth/authorize?client_id=". \$this->api_key ."&scope=".\$scopes."&redirect_uri=". \$redirect_uri ."&state=".\$nonce . "&grant_options[]=per-user";
```

I tested my code by switching from the basic “Minimalist” theme to a OS2.0 theme called “Dawn”. When the app is being installed, I do a few things to check if app blocks are supported:

1. Get a list of the merchant’s installed themes, and check which is currently published
2. Retrieve a list of assets in the published theme
3. Check if JSON template files exist for at least one of the desired templates
```\$params = [];
\$json_string_params = json_encode(\$params);
'X-Shopify-Access-Token:' . \$access_token,
'content-type: application/json'
);

// \$install_ctc_curl_response_json = \$this->curlApiUrl("https://www.splitwit.com/service-layer/click-to-call-service.php?method=installShopifyApp&installation_complete_id=".\$installation_complete_id, \$params);

// check if this merchant's published theme supports app blocks
\$read_themes_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2021-10/themes.json"; // list of all installed themes
\$published_theme_id = 0;
foreach (\$themes as \$theme) {
// live theme has a role of main
if(\$theme['role'] == "main"){
\$published_theme_id = \$theme['id'];
// echo "The main theme is " . \$theme['name'] . "<br /><br /><br />";
}
}

// Retrieve a list of assets in the published theme
\$get_theme_assets_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2021-10/themes/".\$published_theme_id."/assets.json";
\$get_theme_assets_curl_response_json = \$this->curlApiUrl(\$get_theme_assets_url, \$json_string_params, \$headers, false);

// Check if JSON template files exist for at least one of the desired templates
// For other applications, you might want to check that they exist for ALL desired templates
\$assets = \$get_theme_assets_curl_response_json['assets'];
\$probably_block_support = false;
\$templates = ['index', 'cart', 'page.contact', 'product', 'collection'];

foreach (\$assets as \$asset) {
foreach (\$templates as \$template) {
if(\$asset['key'] == "templates/".\$template.".json" ){
\$probably_block_support = true;
break; // this checks that JSON template files exist for at least one of the desired templates. If you want to check that they exist for ALL desired templates, you can move this break to the 'else' condition
}else{
\$probably_block_support = false;
// break;
}
}

if(\$probably_block_support){
break;
}
}
```

1. The body of JSON templates to determine what section is set as `main`
2. The content of each `main` section and if it has a schema that contains a block of type ‘@app’

```<?php
// we can continue further checks here
// https://shopify.dev/apps/online-store/verify-support

if(\$probably_block_support){

foreach (\$templates as \$template) {
\$get_single_asset_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2021-10/themes/".\$published_theme_id."/assets.json?asset[key]=templates/".\$template.".json";

\$get_single_asset_curl_response_json = \$this->curlApiUrl(\$get_single_asset_url, \$json_string_params, \$headers, false);
// var_dump(\$get_single_asset_curl_response_json['asset']['value']);
\$asset_value_json = json_decode(\$get_single_asset_curl_response_json['asset']['value']);
var_dump(\$asset_value_json->sections);
echo "<hr />";
// break;
}
}
```

From my testing, those last two steps were not reliable and ultimately irrelevant.

If app blocks are supported, the snippet is not created nor injected through the Shopify script_tag API. I make note of it in the database.

```\$timestamp = time();
\$snippet = md5(\$timestamp);
\$snippet = \$snippet . ".js";
\$using_app_blocks = 0;

// don't create the snippet if we think they have app block support
if (\$probably_block_support) {
\$using_app_blocks = 1;

}else{

// create snippet file
\$myfile = fopen("/var/www/html/click-to-call/snippet/".\$snippet, "w");
fclose(\$myfile);

// inject JS snippet into site
\$create_script_tag_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2020-04/script_tags.json";
\$params = [
'script_tag' => [
'src' => 'https://www.splitwit.com/click-to-call/snippet/' . \$snippet
]
];
\$json_string_params = json_encode(\$params);
}

\$stmt = \$conn->prepare("INSERT INTO `prompts` (shopify_installation_complete_id, snippet, shop, using_app_blocks, access_token) VALUES (:shopify_installation_complete_id, :snippet, :shop, :using_app_blocks, :access_token)");
\$stmt->bindParam(':shopify_installation_complete_id', \$installation_complete_id);
\$stmt->bindParam(':snippet', \$snippet);
\$stmt->bindParam(':shop', \$shop);
\$stmt->bindParam(':using_app_blocks', \$using_app_blocks);
\$stmt->bindParam(':access_token', \$access_token);
\$stmt->execute();
```

I check for that value from the front-end to display steps for integrating the app block from the theme customizer. Shopify guidelines require that we provide merchants with post-installation onboarding instructions. Those directions replace the settings input form. Configurations will be managed through the block itself.

Although Shopify does provide recommendations for merchant onboarding, there is no boiler-plate copy. A basic explanation, with screenshots, sufficed.

These updates satisfied the app review team’s request. I responded to their email, and a day later received their reply. It complained that the “app doesn’t have a functional user interface (UI)” when app blocks are enabled. That was because all of the settings were being managed by the app block data. To solve this issue, I moved the phone number and message fields back to the app’s settings view. I saved those values as metafields using the Shopify REST API.

```// write this data to custom metafields, so we can access it from app blocks
\$clickToCallRecord = \$this->getClickToCallRecord(\$shopify_installation_complete_id);
\$access_token = \$clickToCallRecord['access_token'];
\$shop = \$clickToCallRecord['shop'];
\$params = [];
\$params = [
"metafield" => [
"namespace" => "clicktocall",
"key" => "phone_number",
"value" => \$_POST['phone_number'],
"type" => "string"
],
"metafield" => [
"namespace" => "clicktocall",
"key" => "message_text",
"value" => \$_POST['message_text'],
"type" => "string"
],
];
\$json_string_params = json_encode(\$params);
'X-Shopify-Access-Token:' . \$access_token,
'content-type: application/json'
);

\$url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2021-10/metafields.json";
```

Update: The Shopify metafield POST API would only create/update a single metafield per request. I had to break it out into two calls. From what I’ve read it seems like a PUT request might do that trick for multiple fields, but for my use-case this approach is fine. Here you can see how I do it when setting default metadata on installation:

```\$params = [
"metafield" => [
"namespace" => "clicktocall",
"key" => "phone_number",
"value" => "(555)-555-5555",
"type" => "string"
]
];
\$json_string_params = json_encode(\$params);

\$url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2021-10/metafields.json";

\$params = [
"metafield" => [
"namespace" => "clicktocall",
"key" => "message_text",
"value" => "Give us a call!",
"type" => "string"
],
];
\$json_string_params = json_encode(\$params);

I populated them from the app block liquid file by accessing the global ‘shop’ object:

```<a style="color:{{block.settings.color}} " href="tel:{{ shop.metafields.clicktocall.phone_number.value }}">{{ shop.metafields.clicktocall.message_text.value }}</a>
```

After another response, they commented that I should be using “App Embed Blocks” instead of just “App Blocks”. That was because my UI component is a “floating or overlaid element”. It exists outside of the normal DOM flow and was not inline with other HTML nodes. This only required a small update to the liquid file’s schema, changing the “target” from “section” to “body”.

Although only a small difference, it does affect how merchants add Click To Call in the theme customizer. They must navigate to the Theme Settings area, and add it as an “App Embed”.

Luckily, I’m able to deep link merchants directly to that view from my onboarding instructions. The link also automatically activates my app embed. All I needed to do was get the extension’s UUID by running shopify extension info from my command line and I was able to build the URL.

```Add the Click To Call App Block from <a href="https://<?php echo \$clickToCallRecord['shop']; ?>/admin/themes/current/editor?context=apps&activateAppId=b52ccd8e-54b1-4b6d-a76f-abaed45dea97/click-to-call" target="_blank">the theme editor</a>
```

I updated my app’s home screen onboarding instructions to reflect this new flow. Everything appeared to be working when I tested, yet the app review team complained that the above issues were still unresolved. It turns out, I was able to immediately see changes to the extension that I pushed from the CLI because “development store preview” was enabled. The review team could not until I published a new version:

After that fix, the app was accepted to the Shopify App Market. If you are a Shopify merchant, check it out and let me know what you think.

## cURL PHP Abstraction

cURL is a PHP library that lets you make HTTP requests. To use it, you need to install the libcurl package. If you’re using PHP on web server, it’s probably already there. The cURL functions have a number of options, depending on what request you’d like to make. You can read all about it in the PHP official documentation.

I had a number of projects that was using it all over the place, to make a variety of requests. I was repeating a lot of code, and my mix-matching began to get confusing. Finally, I wrote a function that takes request options as arguments and does all the work:

```public function curlApiUrl(\$url, \$params, \$headers = false, \$http_verb = false){

\$curl_connection = curl_init();
// curl_setopt(\$curl_connection, CURLOPT_FOLLOWLOCATION, true);
}
curl_setopt(\$curl_connection, CURLOPT_URL, \$url);

if(\$http_verb == "post"){
curl_setopt(\$curl_connection, CURLOPT_POST, true);
curl_setopt(\$curl_connection, CURLOPT_POSTFIELDS, \$params);
}
if(\$http_verb == "delete"){
curl_setopt(\$curl_connection, CURLOPT_CUSTOMREQUEST, "DELETE");
}
if(\$http_verb == "put"){
curl_setopt(\$curl_connection, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt(\$curl_connection, CURLOPT_POSTFIELDS, \$params);
}
//end TODO
curl_setopt(\$curl_connection, CURLOPT_RETURNTRANSFER, true);
\$curl_response = curl_exec(\$curl_connection);
\$curl_response_json = json_decode(\$curl_response,true);
curl_close(\$curl_connection);
return \$curl_response_json;
}
```

I used this as a class method in the back-end services to create Shopify apps. It’s implementation looks like this:

```\$create_recurring_charge_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2020-04/recurring_application_charges.json";
\$params = [
'recurring_application_charge' => [
'name' => 'Basic Plan',
'price' => 25.0,
// 'return_url' => "https://" . \$shop . "/admin/apps/splitwit",
// 'test' => true,
'return_url' => "https://www.splitwit.com/service-layer/shopify-app-service?method=confirmSubscription"
]
];
'X-Shopify-Access-Token: ' . \$access_token,
'content-type: application/json'
);
\$json_string_params = json_encode(\$params);

```

cURL’s default request type is always GET. If I want to use a different HTTP verb, I can specify it as an argument. Adding this function as an abstraction layer over existing methods helps me get things done more quickly, and kept my code clear, clean, and under control.

## Radio button value checked from a database in PHP

When building software user interfaces, I create HTML forms to manage any app settings. I’ll usually have a few text and radio inputs like this:

Using PHP, I grab a database record and access its properties to populate the form. Filling out the text inputs is straight-forward:

```<?php \$clickToCallRecord = \$db_service->getClickToCallRecord(\$installation_complete_id); ?>

<div class="form-group">
<label>Phone number:</label>
<input class="form-control" type="tel" name="phone_number" value="<?php echo \$clickToCallRecord['phone_number']; ?>" placeholder="(123)-456-7890">
</div>
<div class="form-group">
<label>Message:</label>
<input class="form-control" type="text" name="message_text" value="<?php echo \$clickToCallRecord['message_text']; ?>" placeholder="Call Us Now">
</div>
<div class="form-group">
<label>Background Color:</label>
<input class="form-control" type="text" value="<?php echo \$clickToCallRecord['bg_color']; ?>" name="bg_color">
</div>
```

Checking the correct radio input requires our code to evaluate the data value. I add a PHP variable to each of the inputs as attributes. Those variables will render to either “checked” or blank:

```<?php
\$top_checked = "";
\$bottom_checked = "";
\$position = \$clickToCallRecord['position'];
if(\$position == "top"){
\$top_checked = "checked";
\$bottom_checked = "";
}else{
\$top_checked = "";
\$bottom_checked = "checked";
}

\$show_checked = "";
\$hide_checked = "";
\$display = \$clickToCallRecord['display'];
if(\$display == "show"){
\$show_checked = "checked";
\$hide_checked = "";
}else{
\$show_checked = "";
\$hide_checked = "checked";
}

?>

<div class="form-group">
<label>Position:</label>
<div>
<div class="form-check form-check-inline">
<input <?php echo \$top_checked; ?> class="form-check-input" type="radio" name="position" value="top">
<label class="form-check-label" for="matchtype">Top</label>
</div>
<div class="form-check form-check-inline">
<input <?php echo \$bottom_checked; ?> class="form-check-input" type="radio" name="position"  value="bottom">
<label class="form-check-label" for="matchtype">Bottom</label>
</div>
</div>
</div>
<hr />
<div class="form-group">
<label>Display:</label>
<div>
<div class="form-check form-check-inline">
<input <?php echo \$show_checked; ?> class="form-check-input" type="radio" name="display" value="show">
<label class="form-check-label" for="matchtype">Show</label>
</div>
<div class="form-check form-check-inline">
<input <?php echo \$hide_checked; ?> class="form-check-input" type="radio" name="display"  value="hide">
<label class="form-check-label" for="matchtype">Hide</label>
</div>
</div>
</div>
```

This example comes from a Shopify app that helps stores increase conversions by adding a UI element prompting customers to call a phone number.

## How to create a Shopify app with PHP

Developing a marketplace app for your SAAS will grow organic traffic and lets users find you. Potential customers can discover your digital product where they are already looking. Software services that occupy the eCommerce space have a chance to help Shopify store owners grow their businesses. Creating a public Shopify app benefits both developers and the merchant user-base in the Shopify App Store ecosystem.

Recently, Shopify announced that it is decreasing the share of profit that it takes from developers. Each year, developers keep all of their revenue up to the first one-million dollars.

## Why Build a Marketplace App?

A few years ago, I built a fitness tracking app for a niche sport. It was a hobby project to better track my BJJ training.  Since then, I continue to average ~10 registrations weekly without any marketing efforts.

Consistent BJJ Tracker sign-ups are driven from Google Play. Even though it is only a web app (PWA), I was able to bundle it into an APK file using Trusted Web Activities and Digital Asset Links. Having an app listed in a marketplace leads to new users finding it naturally.

My next side project was a SAAS for split testing & conversion optimization. It helps websites A/B test to figure out what front-end changes lead to more sales, sign-ups, etc. The Shopify App Store was as perfect fit to attract shop owners to use SplitWit. I decided to build a public, embedded, Shopify app to reach new prospects.

I’ll explain how I did it, along with examples of building another one, all using PHP. This guide will make launching a Shopify App Store app easy, fast, repeatable.

## Creating a Public Shopify App with PHP

Embedded Shopify apps display in a merchant’s admin dashboard. They are meant to “add functionality to Shopify stores“. They are hosted on the developer’s infrastructure and are loaded via iFrame within Shopify. You can create a new app in your Shopify Partners dashboard to get started.

Since the SplitWit SAAS already existed as a subscription web app built on the LAMP stack, I only had to handle Shopify specific authorization and payments. I could essentially load the existing app in the dashboard’s iFrame after authentication. The new code I wrote contains methods for checking installation status, building the oAuth URL, subscribing users to recurring application charges, and more.

When I created my next Shopify app, Click to Call, I leveraged that code and refactored it to be more reusable. Configurable parameters let me set the database and API credentials dynamically.

### Step 1: Check Installation Status

When the app’s url loads in the merchant admin dashboard, the first step is to check installation status for that shop.

```public function checkInstallationStatus(){
\$conn = \$this->conn;
\$shop = \$_GET['shop'];

//check if app is already installed or not
\$statement = \$conn->prepare("SELECT * FROM `shopify_installation_complete` WHERE shop = :shop");
\$statement->execute(['shop' => \$shop]);
\$count = \$statement->rowCount();
if(\$count == 0){
//app is not yet installed
return false;
}else{
\$row = \$statement->fetch();
return \$row;
}

}
```

You’ll notice we don’t hit any Shopify API for this. Instead, our own database is queried. We manually track if the app has already been installed in a MySql table.

Two database tables are used to manage the Shopify App installation:

```CREATE TABLE IF NOT EXISTS `shopify_authorization_redirect` (
`shopify_authorization_redirect_id` int NOT NULL AUTO_INCREMENT,
`shop` varchar(200),
`nonce` varchar(500),
`scopes` varchar(500),
created_date datetime DEFAULT CURRENT_TIMESTAMP,
updated_date datetime ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`shopify_authorization_redirect_id`)
);

CREATE TABLE IF NOT EXISTS `shopify_installation_complete` (
`shopify_installation_complete_id` int NOT NULL AUTO_INCREMENT,
`splitwit_account_id` int,
`splitwit_project_id` int,
`shop` varchar(200),
`access_token` varchar(200),
`scope` varchar(200),
`expires_in` int,
`associated_user_scope` varchar(200),
`associated_user_id` BIGINT,
`associated_user_first_name` varchar(200),
`associated_user_last_name` varchar(200),
`associated_user_email` varchar(200),
`associated_user_email_verified` varchar(10),
`associated_user_account_owner` varchar(10),
`associated_user_account_locale` varchar(10),
`associated_user_account_collaborator` varchar(10),
created_date datetime DEFAULT CURRENT_TIMESTAMP,
updated_date datetime ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`shopify_installation_complete_id`)
);
```

The root file (index.php) looks like this:

```<?php

require '/var/www/html/service-layer/shopify-app-service.php';
include 'shopify-creds.php'; // \$api_key, \$secret, \$app_db, \$app_slug
use SplitWit\ShopifyService\ShopifyService;
\$shopify_service = new ShopifyService(\$api_key, \$secret, \$app_db);

\$install_redirect_url = \$shopify_service->buildAuthorizationUrl(false, \$app_slug);
}else{
\$install_redirect_url = \$shopify_service->buildAuthorizationUrl(true, \$app_slug);
}

?>
```

After determining the merchant’s installation status, the next step is to authenticate them.

### oAuth Authentication

The Shopify developer resources explain “how to ask for permission” with oAuth. The merchant user needs to be redirected to a Shopify URL:

```https://{shop}.myshopify.com/admin/oauth/authorize?client_id={api_key}&scope={scopes}&redirect_uri={redirect_uri}&state={nonce}&grant_options[]={access_mode}
```

I wrote a PHP class “ShopifyService” (shopify-app-service.php) to handle all of the Shopify specific logic. The method buildAuthorizationUrl() builds the Shopify authorization URL. It accepts a boolean parameter set according to the merchant’s installation status. That value toggles the authorization URL’s redirect URI, directing the code flow through either first-time installation or re-authentication.

The built URL includes query params: an API key, a nonce, the scope of permission being requested, and a redirect URI. The shop name is the subdomain, and can be pulled as GET data delivered to your app.

The API key can be found in the developer’s partner dashboard in the app’s overview page.

A nonce (“number used once”) is used as an oAuth state parameter. It serves to “link requests and callbacks to prevent cross-site request forgery attacks.” We save that random value in our database to check against during the oAuth callback.

The redirect URI (the oAuth callback) is dynamic based on the users installation status.

```public function buildAuthorizationUrl(\$reauth = false, \$slug= "shopify-app"){
\$conn = \$this->conn;
\$requestData = \$this->requestData;
\$nonce = bin2hex(random_bytes(10));
\$shop = \$requestData['shop'];

//first check if there is already a record for this shop. If there is, delete it first.
\$statement = \$conn->prepare("SELECT * FROM `shopify_authorization_redirect` WHERE shop = :shop");
\$statement->execute(['shop' => \$shop]);
\$count = \$statement->rowCount();

if(\$count > 0){
\$statement = \$conn->prepare("DELETE FROM `shopify_authorization_redirect` WHERE shop = :shop");
\$statement->execute(['shop' => \$shop]);
}

\$statement = \$conn->prepare("INSERT INTO `shopify_authorization_redirect` (shop, nonce, scopes) VALUES (:shop, :nonce, :scopes)");
\$statement->bindParam(':shop', \$shop);
\$statement->bindParam(':nonce', \$nonce);
\$statement->bindParam(':scopes', \$scopes);
\$statement->execute();

\$redirect_uri = "https://www.splitwit.com/".\$slug."/authorize-application";

if(\$reauth){ //change the redirect URI
\$redirect_uri = "https://www.splitwit.com/".\$slug."/reauthorize-application";
}

\$redirect_url = "https://".\$shop."/admin/oauth/authorize?client_id=". \$this->api_key ."&scope=".\$scopes."&redirect_uri=". \$redirect_uri ."&state=".\$nonce . "&grant_options[]=per-user";

return \$redirect_url;

}
```

Both possible redirect URLs needed to be white-listed in the “App setup” page.

A location header routes the user. If the app hasn’t been installed yet, Shopify prompts the merchant to confirm authorization.

If the app was already installed then the oAuth grant screen is skipped entirely and the merchant is immediately routed to the /reauthorize-application resource instead (and ultimately lands on the app home screen).

### Install App & Register the Merchant User

What actually happens when “Install app” is clicked?  The user is redirected from Shopify permissions screen back to our app ( /authorize-application ), calling the authorizeApplication() function. That method receives four values as GET parameters: ‘shop’, ‘state’, ‘hmac’, ‘code’

The ‘shop’ name is used to look up the nonce value we saved when the Shopify authorization URL was first built. We compare it to the ‘state’ parameter. This security step ensures that the callback request is valid and is not fraudulent. We also check that the shop name provided matches a valid Shopify hostname. Here is the relevant code, pulled from authorizeApplication():

```\$conn = \$this->conn;
\$requestData = \$this->requestData;
\$requiredKeys = ['code', 'hmac', 'state', 'shop'];
foreach (\$requiredKeys as \$required) {
if (!in_array(\$required, array_keys(\$requestData))) {
throw new Exception("The provided request data is missing one of the following keys: " . implode(', ', \$requiredKeys));
}
}

//lookup and validate nonce
\$shop = \$requestData['shop'];

\$statement = \$conn->prepare("SELECT * FROM `shopify_authorization_redirect` WHERE shop = :shop");
\$statement->execute(['shop' => \$shop]);
\$count = \$statement->rowCount();
if(\$count == 0){
}
\$row = \$statement->fetch();
\$nonce = \$row['nonce'];
//

//make sure the 'state' parameter provided matches the stored nonce
\$state = \$requestData['state'];
if(\$state !== \$nonce){
throw new Exception("Nonce does not match provided state.");
}
//

//validate the shop name
\$pattern = "/[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com[\/]?/";
if(!preg_match(\$pattern, \$shop)) {
throw new Exception("The shop name is an invalid Shopify hostname.");
}```

Every request or redirect from Shopify” includes a HMAC value that can be used to verify its authenticity. Here is how I do it in PHP:

```public function verifyHmac(\$requestData){
// verify HMAC signature.
// https://help.shopify.com/api/getting-started/authentication/oauth#verification
if( !isset(\$requestData['hmac'])){
return false;
}

\$hmacSource = [];

foreach (\$requestData as \$key => \$value) {

if (\$key === 'hmac') { continue; }

// Replace the characters as specified by Shopify in the keys and values
\$valuePatterns = [
'&' => '%26',
'%' => '%25',
];
\$keyPatterns = array_merge(\$valuePatterns, ['=' => '%3D']);
\$key = str_replace(array_keys(\$keyPatterns), array_values(\$keyPatterns), \$key);
\$value = str_replace(array_keys(\$valuePatterns), array_values(\$valuePatterns), \$value);

\$hmacSource[] = \$key . '=' . \$value;
}

sort(\$hmacSource);
\$hmacBase = implode('&', \$hmacSource);
\$hmacString = hash_hmac('sha256', \$hmacBase, \$this->secret);
// Verify that the signatures match
if (\$hmacString !== \$requestData['hmac']) {
return false;
}else{
return true;
}
}
```

That method is called in the class construct function, to be sure it happens every time.

The ‘code’ parameter is the access code. It is exchanged for an access token by sending a request to the shop’s access_token endpoint. We record that token to the ‘shopify_installation_complete’ table along with relevant data.

To fully complete the installation, app specific project records are saved. For SplitWit, this means a user-account is created along with an initial project. Any JavaScript tags are injected onto the merchant’s site using the Shopify admin API ScriptTag resource. Linking a privately hosted JS file, unique to each merchant, allows our app to dynamically update the shop website. You can learn about how that snippet tag works in another post that explains how the visual editor is built.

Lastly, a webhook is created to listen for when this app in uninstalled ( ‘topic’ => ‘app/uninstalled’ ) in order to call our “uninstallApplication” method. Webhooks allow you to listen for certain events in a shop, and run code based on data about what happened.

Once installation is complete, our server returns a header that reloads the app.

Below is the original authorizeApplication() method. Eventually, I moved app specific logic into its own files after refactoring this class to support another SAAS, Click to Call.

```public function authorizeApplication(){
\$conn = \$this->conn;
\$requestData = \$this->requestData;
\$requiredKeys = ['code', 'hmac', 'state', 'shop'];
foreach (\$requiredKeys as \$required) {
if (!in_array(\$required, array_keys(\$requestData))) {
throw new Exception("The provided request data is missing one of the following keys: " . implode(', ', \$requiredKeys));
}
}

//lookup and validate nonce
\$shop = \$requestData['shop'];

\$statement = \$conn->prepare("SELECT * FROM `shopify_authorization_redirect` WHERE shop = :shop");
\$statement->execute(['shop' => \$shop]);
\$count = \$statement->rowCount();
if(\$count == 0){
}
\$row = \$statement->fetch();
\$nonce = \$row['nonce'];
//

//make sure the 'state' parameter provided matches the stored nonce
\$state = \$requestData['state'];
if(\$state !== \$nonce){
throw new Exception("Nonce does not match provided state.");
}
//

//validate the shop name
\$pattern = "/[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com[\/]?/";
if(!preg_match(\$pattern, \$shop)) {
throw new Exception("The shop name is an invalid Shopify hostname.");
}
//

//install the app

//exchange the access code for an access token by sending a request to the shop’s access_token endpoint
\$code = \$requestData['code'];
\$post_url = "https://" . \$shop . "/admin/oauth/access_token";

\$params = [
'client_id'    => \$this->api_key,
'client_secret'    => \$this->secret,
'code'    => \$code
];

\$curl_response_json = \$this->curlApiUrl(\$post_url, \$params);
\$access_token = \$curl_response_json['access_token'];

\$statement = \$conn->prepare("INSERT INTO `shopify_installation_complete` (shop, access_token, scope, expires_in, associated_user_scope, associated_user_id, associated_user_first_name, associated_user_last_name, associated_user_email, associated_user_email_verified, associated_user_account_owner, associated_user_account_locale, associated_user_account_collaborator) VALUES (:shop, :access_token, :scope, :expires_in, :associated_user_scope, :associated_user_id, :associated_user_first_name, :associated_user_last_name, :associated_user_email, :associated_user_email_verified, :associated_user_account_owner, :associated_user_account_locale, :associated_user_account_collaborator)");

\$statement->bindParam(':shop', \$shop);

\$statement->bindParam(':access_token', \$access_token);
\$statement->bindParam(':scope', \$curl_response_json['scope']);
\$statement->bindParam(':expires_in', \$curl_response_json['expires_in']);
\$statement->bindParam(':associated_user_scope', \$curl_response_json['associated_user_scope']);
\$statement->bindParam(':associated_user_id', \$curl_response_json['associated_user']['id']);
\$statement->bindParam(':associated_user_first_name', \$curl_response_json['associated_user']['first_name']);
\$statement->bindParam(':associated_user_last_name', \$curl_response_json['associated_user']['last_name']);
\$statement->bindParam(':associated_user_email', \$curl_response_json['associated_user']['email']);
\$statement->bindParam(':associated_user_email_verified', \$curl_response_json['associated_user']['email_verified']);
\$statement->bindParam(':associated_user_account_owner', \$curl_response_json['associated_user']['account_owner']);
\$statement->bindParam(':associated_user_account_locale', \$curl_response_json['associated_user']['locale']);
\$statement->bindParam(':associated_user_account_collaborator', \$curl_response_json['associated_user']['collaborator']);

\$statement->execute();
\$installation_complete_id = \$conn->lastInsertId();

if(isset(\$curl_response_json['associated_user']['email']) && strlen(\$curl_response_json['associated_user']['email']) > 0){

\$store_name = explode(".", \$shop);
\$store_name = ucfirst(\$store_name[0]);

//create account
\$method = "thirdPartyAuth";
\$user_service_url = "https://www.splitwit.com/service-layer/user-service.php?third_party_source=shopify&method=" . \$method . "&email=".\$curl_response_json['associated_user']['email']."&companyname=" .\$store_name . "&first=" . \$curl_response_json['associated_user']['first_name'] . "&last=" . \$curl_response_json['associated_user']['last_name'] ;

\$params = [];

\$curl_user_response_json = \$this->curlApiUrl(\$user_service_url, \$params);

\$account_id = \$curl_user_response_json['userid'];

\$method = "createProject";

\$project_service_url = "https://www.splitwit.com/service-layer/project-service.php?method=" . \$method . "&accountid=" . \$account_id;

\$params = [
'projectname'    => \$store_name . " Shopify",
'projectdomain'    => "https://".\$shop,
'projectdescription'    => ""
];

\$curl_project_response_json = \$this->curlApiUrl(\$project_service_url, \$params);
\$project_id = \$curl_project_response_json['projectid'];
\$snippet = \$curl_project_response_json['snippet'];

//inject JS snippet into site
\$create_script_tag_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2020-04/script_tags.json";
\$params = [
'script_tag' => [
'src' => 'https://www.splitwit.com/snippet/' . \$snippet
]
];

'X-Shopify-Access-Token:' . \$access_token,
'content-type: application/json'
);

\$json_string_params = json_encode(\$params);

//write accountID and ProjectID to this shopify_installation_complete record.

\$statement = \$conn->prepare("UPDATE `shopify_installation_complete` SET splitwit_account_id = ?, splitwit_project_id = ? WHERE shopify_installation_complete_id = ?");

\$statement->execute(array(\$account_id, \$project_id, \$installation_complete_id));

}

//create webhook to listen for when app in uninstalled.
\$create_webhook_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2020-04/webhooks.json";
\$params = [
'webhook' => [
'topic' => 'app/uninstalled',
'format' => 'json'
]
];

'X-Shopify-Access-Token:' . \$access_token,
'content-type: application/json'
);

\$json_string_params = json_encode(\$params);

//installation complete.
}

}

```

You’ll notice that I call a custom method that abstracts the PHP cURL (client URL library) methods. This helps me avoid repeating the same code in multiple places.

```public function curlApiUrl(\$url, \$params, \$headers = false, \$use_post = true, \$use_delete = false, \$use_put = false){

\$curl_connection = curl_init();
// curl_setopt(\$curl_connection, CURLOPT_FOLLOWLOCATION, true);
}
curl_setopt(\$curl_connection, CURLOPT_URL, \$url);

// TODO: refactor these three conditions into one, that accepts the RESTful request type!!
if(\$use_post){
curl_setopt(\$curl_connection, CURLOPT_POST, true);
curl_setopt(\$curl_connection, CURLOPT_POSTFIELDS, \$params);
}
if(\$use_delete){
curl_setopt(\$curl_connection, CURLOPT_CUSTOMREQUEST, "DELETE");
}
if(\$use_put){
curl_setopt(\$curl_connection, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt(\$curl_connection, CURLOPT_POSTFIELDS, \$params);
}
//end TODO

curl_setopt(\$curl_connection, CURLOPT_RETURNTRANSFER, true);
\$curl_response = curl_exec(\$curl_connection);
\$curl_response_json = json_decode(\$curl_response,true);
curl_close(\$curl_connection);
return \$curl_response_json;
}
```

The merchant will be able to find the app in the ‘Apps’ section of their dashboard. Shopify remembers that permission was granted by the merchant.

The oAuth grant screen will not show again when the app is selected in the future.  As the installation status returns true, our code will flow into the reAuthenticate() method. The same validation checks are performed and a new access token is received.

```
public function reAuthenticate(){
\$conn = \$this->conn;
\$requestData = \$this->requestData;
\$requiredKeys = ['code', 'hmac', 'state', 'shop'];
foreach (\$requiredKeys as \$required) {
if (!in_array(\$required, array_keys(\$requestData))) {
throw new Exception("The provided request data is missing one of the following keys: " . implode(', ', \$requiredKeys));
// return;
}
}

//lookup and validate nonce
\$shop = \$requestData['shop'];

\$statement = \$conn->prepare("SELECT * FROM `shopify_authorization_redirect` WHERE shop = :shop");
\$statement->execute(['shop' => \$shop]);
\$count = \$statement->rowCount();
if(\$count == 0){
}
\$row = \$statement->fetch();
\$nonce = \$row['nonce'];
//

//make sure the 'state' parameter provided matches the stored nonce
\$state = \$requestData['state'];
if(\$state !== \$nonce){
throw new Exception("Nonce does not match provided state.");
}
//

//validate the shop name
\$pattern = "/[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com[\/]?/";
if(!preg_match(\$pattern, \$shop)) {
throw new Exception("The shop name is an invalid Shopify hostname.");
}

//exchange the access code for an access token by sending a request to the shop’s access_token endpoint
\$code = \$requestData['code'];
\$post_url = "https://" . \$shop . "/admin/oauth/access_token";

\$params = [
'client_id'    => \$this->api_key,
'client_secret'    => \$this->secret,
'code'    => \$code
];

\$curl_response_json = \$this->curlApiUrl(\$post_url, \$params);
\$access_token = \$curl_response_json['access_token'];

\$statement = \$conn->prepare("UPDATE `shopify_installation_complete` SET access_token = ? WHERE shop = ?");
\$statement->execute(array(\$access_token, \$shop));

}
```

The merchant is routed to the app’s /home location. A few session variables are set and the user interface is loaded.

```<?php
require '/var/www/html/service-layer/shopify-app-service.php';
use SplitWit\ShopifyService\ShopifyService;
\$shopify_service = new ShopifyService();
?>
<style>
.back-to-projects{
display: none;
}
</style>

<body class="dashboard-body">
<?php
// log the user out...
\$sess_service = new UserService();
\$sess_service -> logout();
//logout destroys the session. make sure to start a new one.
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// ...then log them in
\$projectid = \$shopify_service->splitwit_project_id;
\$accountid = \$shopify_service->splitwit_account_id;
\$_SESSION['accountid'] = \$accountid;
\$_SESSION['userid'] =  \$accountid;

\$_SESSION['active'] = true;
include '/var/www/html/includes/experiments-ui.php';

?>
</body>
</html>

```

The method makeSureRecordsExist() checks that the SplitWit user account and project records exist, as a failsafe. The .back-to-projects CTA is hidden because Shopify users only have access to one project for their shop. The app is installed for free, while premium functionality requires a subscription after a one-week trial.

When building my second Shopify app, I started with an empty home screen UI:

```<?php
require '/var/www/html/service-layer/shopify-app-service.php';
include 'shopify-creds.php';
use SplitWit\ShopifyService\ShopifyService;
\$shopify_service = new ShopifyService(\$api_key, \$secret, \$app_db);
?>
<html>
<body>

</body>
<script>

if(self!==top){
// if loaded outside of an iframe, redirect away to a marketing page
window.location = "https://www.splitwit.com";
}

</script>
</html>
```

### Subscription Payment

SplitWit’s codebase is originally used as a non-Shopify, stand-alone, web app SAAS. It uses Stripe as a payment gateway. Shopify requires apps to use their Billing API instead. To remedy this, I’m able to write Shopify specific front-end code with a simple JavaScript check. I leverage the browser’s window.self property to check if my app’s code is running in the top most window (opposed to being nested in an iFrame).

```if(self!==top){
// shopify app
\$(".activate-cta").remove();
if(window.pastDueStatus || window.customerId.length === 0){

\$.ajax({
url:"/service-layer/shopify-app-service?method=createRecurringApplicationCharge",
complete: function(response){
console.log(response)
//user is redirected to shopify confirmation screen
\$(".activate-cta-top").attr("href", response.responseText).show();
}
});
}

\$(".back-cta").click(function(){
window.history.back();
})
\$(".reset-pw-cta").hide()

}else{

\$(".activate-cta").click(function(){
\$(".stripe-payment-modal").show();
});

\$(".back-cta").hide();
}
```

If it’s not the top most window, I assume the code is running in Shopify. I’ll change the click-event listener on the .activate-cta element to create a recurring subscription charge. An AJAX call is made to our PHP end-point that hits Shopify’s RecurringApplicationCharge API.

```public function createRecurringApplicationCharge(){

\$conn = \$this->conn;
\$statement = \$conn->prepare("SELECT * FROM `shopify_installation_complete` WHERE splitwit_account_id = :splitwit_account_id");
\$statement->execute(['splitwit_account_id' => \$_SESSION['accountid']]);
\$row = \$statement->fetch();
\$shop = \$row['shop'];
\$access_token = \$row['access_token'];

\$create_recurring_charge_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2020-04/recurring_application_charges.json";
\$params = [
'recurring_application_charge' => [
'name' => 'Basic Plan',
'price' => 25.0,
// 'return_url' => "https://" . \$shop . "/admin/apps/splitwit",
// 'test' => true,
'return_url' => "https://www.splitwit.com/service-layer/shopify-app-service?method=confirmSubscription"
]
];
'X-Shopify-Access-Token: ' . \$access_token,
'content-type: application/json'
);
\$json_string_params = json_encode(\$params);

echo \$create_recurring_charge_curl_response_json['recurring_application_charge']['confirmation_url'];
}
```

The charge ID (delivered by the Shopify request to our ‘return_url’), payment processor, and subscription expiry date are saved to our database on call-back before returning a location header to reload the app.

```public function confirmSubscription(){

\$conn = \$this->conn;
\$statement = \$conn->prepare("SELECT * FROM `shopify_installation_complete` WHERE splitwit_account_id = :splitwit_account_id");
\$statement->execute(['splitwit_account_id' => \$_SESSION['accountid']]);
\$row = \$statement->fetch();
\$shop = \$row['shop'];

\$charge_id = \$_REQUEST['charge_id'];
//write shopify billing ID to db
\$sql = "UPDATE `account` SET payment_processor = ?, billing_customer_id = ?, current_period_end = ?, past_due = 0 WHERE accountid = ?";
\$result = \$conn->prepare(\$sql);
\$current_period_end = new \DateTime();  //we need the slash here (before DateTime class name), since we're in a different namespace (declared at the top of this file)
\$current_period_end->modify( '+32 day' );
\$current_period_end = \$current_period_end->format('Y-m-d H:i:s');
\$payment_processor = "shopify";
\$result->execute(array(\$payment_processor, \$charge_id, \$current_period_end, \$_SESSION['accountid']));

//redirect to app

}
```

That charge ID (saved to our database in a column titled “billing_customer_id”) can later be passed back to Shopify to delete the recurring charge.

### Cancel a Subscription

Once a subscription is active, I can check  the payment processor saved the the account’s DB record to toggle the “cancel account” functionality from Stripe to Shopify.

```<?php if (\$account_row['payment_processor'] == "shopify"){ ?>
//hit shopify service

\$(".cancel-cta").click(function(){
//
\$.ajax({
url:"/service-layer/shopify-app-service?method=cancelSubscription",
complete: function(response){
}
});
});

<?php }else{ ?>
//hit the stripe service

\$(".cancel-cta").click(function(){
\$(".cancel-subscription-modal").show();
});

<?php }?>
```

The cancelSubscription method hits the same Shopify recurring_application_charges API, but uses a DELETE request. It also deletes the Shopify billing ID from our records.

```public function cancelSubscription(){

\$conn = \$this->conn;
\$statement = \$conn->prepare("SELECT * FROM `shopify_installation_complete` WHERE splitwit_account_id = :splitwit_account_id");
\$statement->execute(['splitwit_account_id' => \$_SESSION['accountid']]);
\$row = \$statement->fetch();
\$shop = \$row['shop'];
\$access_token = \$row['access_token'];

\$statement = \$conn->prepare("SELECT * FROM `account` WHERE accountid = :accountid");
\$statement->execute(['accountid' => \$_SESSION['accountid']]);
\$account_row = \$statement->fetch();
\$charge_id = \$account_row['billing_customer_id'];

\$delete_recurring_charge_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2020-04/recurring_application_charges/#" . \$charge_id . ".json";

\$params = [];
'X-Shopify-Access-Token: ' . \$access_token,
'content-type: application/json'
);
\$json_string_params = json_encode(\$params);
\$delete = true;

\$delete_recurring_charge_curl_response_json = \$this->curlApiUrl(\$delete_recurring_charge_url, \$json_string_params, \$headers, \$delete);

//delete shopify billing ID from db
\$empty_string = "";
\$sql = "UPDATE `account` SET payment_processor = ?, billing_customer_id = ? WHERE accountid = ?";
\$result = \$conn->prepare(\$sql);
\$result->execute(array(\$empty_string, \$empty_string, \$_SESSION['accountid']));

echo \$delete_recurring_charge_curl_response_json;

}
```

I can use these same recurring application API end-point functions with minimal adjustments for other Shopify apps that I build. After refactoring, I am able to specify an app database as a GET parameter in the AJAX calls to my Shopify PHP service.

### Uninstall the App

Merchants can choose to delete apps from their shop. This will remove it from their list of installed apps. If they try installing it again, they will be re-promoted for permissions. When an app is deleted, a webhook is notified so that code can handle server-side uninstall logic:

The payment processor and billing ID associated with the merchant’s account is set to an empty string. The ‘shopify_installation_complete’ shop record is deleted.

```public function uninstallApplication(){
\$conn = \$this->conn;

\$res = '';
\$data = file_get_contents('php://input'); //similar to \$_POST
\$decoded_data = json_decode(\$data, true);

if( \$verified == true ) {
if( \$topic_header == 'app/uninstalled' ) {
\$domain = \$decoded_data['domain'];

\$statement1 = \$conn->prepare("SELECT * FROM `shopify_installation_complete` WHERE shop = ?");
\$statement1->execute(array(\$domain));
\$row = \$statement1->fetch();
\$accountid = \$row['splitwit_account_id'];

//delete shopify billing ID from db
\$empty_string = "";
\$result = \$conn->prepare("UPDATE `account` SET payment_processor = ?, billing_customer_id = ? WHERE accountid = ?");
\$result->execute(array(\$empty_string, \$empty_string, \$accountid));

\$statement = \$conn->prepare("DELETE FROM `shopify_installation_complete` WHERE shop = ?");
\$statement->execute(array(\$domain));

} else {
\$res = \$data;
}
}
} else {
\$res = 'The request is not from Shopify';
}

}
```

Any webhook requests have the HMAC delivered as a header (instead of a query param, as in the case of oAuth requests) and is processed differently. “The HMAC verification procedure for OAuth is different from the procedure for verifying webhooks“. The method verifyWebhook() takes care of it:

```public function verifyWebhook(\$data, \$hmac_header){
\$calculated_hmac = base64_encode(hash_hmac('sha256', \$data, \$this->secret, true));
}
```

### Cache Busting

When project changes are recorded in the app, the merchant’s snippet file is updated. We need to be sure that their website recognizes the latest version. In a separate class (that handles project & snippet logic) I make a HTTP request to my method that re-writes the script tag.

```public function updateSnippetScriptTag(){
\$projectid = \$_GET['projectid'];
\$conn = \$this->conn;
\$sql = "SELECT * FROM `shopify_installation_complete` WHERE splitwit_project_id = ?";
\$result = \$conn->prepare(\$sql);
\$result->execute(array(\$projectid));
\$row = \$result->fetch(\PDO::FETCH_ASSOC);
\$number_of_rows = \$result->rowCount();
if(\$number_of_rows == 1){
\$access_token = \$row['access_token'];
\$shop = \$row['shop'];
\$sql = "SELECT * FROM `project` WHERE projectid = ?";
\$project_result = \$conn->prepare(\$sql);
\$project_result->execute(array(\$projectid));
\$project_row = \$project_result->fetch(\PDO::FETCH_ASSOC);
\$snippet = \$project_row['snippet'];

\$script_tag_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2020-04/script_tags.json";
'X-Shopify-Access-Token:' . \$access_token,
'content-type: application/json'
);
\$params = [];
\$json_string_params = json_encode(\$params);
\$use_post = false;
//get existing script tag
\$get_script_curl_response_json = \$this->curlApiUrl(\$script_tag_url, \$json_string_params, \$headers, \$use_post);
\$tags = \$get_script_curl_response_json['script_tags'];

foreach (\$tags as \$tag) {
\$id = \$tag['id'];
\$delete_script_tag_url = "https://" . \$this->api_key . ":" . \$this->secret . "@" . \$shop . "/admin/api/2020-04/script_tags/" . \$id . ".json";
\$use_delete = true;
\$delete_script_curl_response_json = \$this->curlApiUrl(\$delete_script_tag_url, \$json_string_params, \$headers, \$use_post, \$use_delete);
}

\$snippet = "https://www.splitwit.com/snippet/" . \$snippet . "?t=" . time();
\$params = [
'script_tag' => [
'src' => \$snippet
]
];
\$json_string_params = json_encode(\$params);

}
}
```

Once our Shopify app is built and tested we can begin to prepare for submission to the Shopify App Market.

## Preparing for production

Shopify allows you to test your app on a development store.

After debugging your code locally, make sure it works end-to-end in Shopify’s environment.

Even though the app is “unlisted”, and has not yet been accepted into the Shopify App Market, you’ll still be able to work through the entire UX flow.

## GDPR mandatory webhooks

Each app developer is responsible for making sure that the apps they build for the Shopify platform are GDPR compliant.” Every app is required to provide three webhook end-points to help manage the data it collects. These end-points make requests to to view stored customer data, delete customer data, and delete shop data.  After handling the request, an HTTP status of 200/OK should be returned. PHP lets us do that with its header() function:

`header("HTTP/1.1 200 OK");`

These GDPR webhook subscriptions can be managed on the “App setup” page.

## App Listing

Before submitting your app to the Shopify App Market, you’ll need to complete “Listing Information”. This section includes the app’s name, icon, description, pricing details, and more. It is encouraged to include screenshots and a demonstration video. Detailed app review instructions, along with screenshots and any on-boarding information, will help move the approval process along more quickly.

## Approval Process

Complete the setup and listing sections, and submit your app.

You’ll receive an email letting you know that testing will begin shortly.

You may be required to make updates based on feedback from Shopify’s review process. After making any required changes, your application will be listed on the Shopify App Store. Below is an example of feedback that I had received:

To remedy the first required change I added additional onboarding copy to the app’s listing and included a demonstration YouTube video.

The second point was fixed by stopping any links from opening in new tabs. (Although, the reviewer’s note about ad blocking software stopping new tabs from opening is bogus).

The third issue was resolved by making sure the graphic assets detailed in my app listing were consistent.

Soon after making these changes, my app was finally approved and listed.

## Keep Building

While writing this article I extended and refactored my PHP code to support multiple apps. I added configuration files to keep database settings modular. The Shopify PHP class can serve as back-end to several implementations. If you have any questions about how to build a Shopify app, or need my help, send me a message.

Update:

I wrote a subsequent post about building another Shopify app. It’s called SplitWit Click to Call. It explains the creative details that go into shipping a fulling working SAAS. I dive into new features that are only available to Shopify themes running the latest OS2.0 experience.