How to Create a High Converting Sales Page Using PHP, Twilio Messaging, Google's App Engine, and Stripe's API

March 15, 2022
Written by
Ijeoma Nelson
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

How to Create a High Converting Sales Page Using PHP, Twilio Messaging, Google’s App Engine, and Stripe’s API

Today's fast-paced, digital economy has created a culture where people want to know how to compete in the global market, what skills are emerging, and how best to learn them.

Consequently, more and more people are choosing educational resources, such as ebooks and video tutorials, to help us fill the knowledge gap necessary to improve our lot in life.

And why not? Whether it's resume writing, DIY, or personal budgeting, there's a guru ready to take you from novice to ninja — "in just seven days!"

Forecast to be worth $350 Billion by 2025, the online learning industry has not only taken the world by storm, it has also created new opportunities for everyday people to become digital entrepreneurs.

With the abundance of e-commerce platforms, website builders, email marketing platforms, and content management systems readily available, just about anyone can launch a sales page and start selling e-courses or ebooks.

How does all this relate to sales pages?

A sales page is a web page designed to persuade your visitors to buy your ebook or enroll in your online course. In other words, a sales page has one specific goal: to convert.

The copy and images must both align with your core offer because this is the point in your visitor's journey where they'll decide whether or not they want to buy your product.

The sales page can make or break the success of your products. Poor execution will result in little to no sales, but get the formula right, and you won't be able to stop hitting refresh on your Stripe account.

That said, the yearly cost of renting other people's platforms often runs into the thousands, making this method of earning unattainable for most, until now.

In this tutorial, I'll be leveling the playing field by showing you exactly how to create and host a high-converting sales page, so that you can launch a potentially lucrative business teaching your skills to others.

Prerequisites

This tutorial assumes a basic understanding of HTML, CSS, Bootstrap, and PHP. In addition, to follow along, you need the following:

Anatomy of a high converting sales page

There are two main types of sales pages: short and long-form. Although each has its appropriate use case, they follow a similar structure to the one below.

Anatomy of a high-converting sales page

The sales page begins with a compelling headline followed by a sub-headline. Then comes a primary image or video which explains the offer, followed by an accompanying Call To Action (CTA) button.

The sales copy section comes next, closely followed by testimonies as social proof. Lastly, you have the payment form with the CTA payment button.

Now that you know what a high converting sales page is, it's time to create one.

Create the project

To start, you need to create the project’s root directory, named salespage and then switch to it. Do this by running the following commands.

mkdir salespage
cd salespage

Next, you need to create some additional files and directories inside the top-level directory:

  • A directory for the JavaScript, CSS, and image files
  • index.php: which will contain the code for the sales page
  • payment.php: which will contain the code that processes the payment form and triggers the Twilio messaging API
  • success.php: where the payment form will redirect to once the payment is successful

To do all of this, run the following commands in the terminal.

mkdir -p css img js
touch js/payment.js css/style.css index.php payment.php success.php

Install the required dependencies

Next, run the following commands to install the Stripe and Twilio SDKs as the project’s dependencies.

composer require stripe/stripe-php twilio/sdk

Create the sales page

Open index.php in your preferred code editor and paste the following code into it.

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <link rel="stylesheet" href="css/style.css">
 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
 <link href="https://fonts.googleapis.com/css2?family=Caveat:wght@700&family=Oswald:wght@200;300;400&family=Poppins:wght@300;500;700&display=swap" rel="stylesheet">
 <title>The 7 Day Email Bootcamp</title>
</head>

<body>
 <div class="container text-center">

   <!-- hero section -->
   <section class="hero-section section-style">
     <div class="px-4 my-5 text-center">
       <h1 class="display-4 fw-bold">The 7 Day Email Bootcamp</h1>
       <div class="col-lg-6 mx-auto">
         <p class="lead mb-4">Lorem ipsum dolor sit, amet consectetur adipisicing elit. In ratione, obcaecati ullam similique ipsa minus.</p>
       </div>
       <div>
         <div class="container px-5">
           <img src="./img/email_marketing.jpg" class="img-fluid border rounded-3 shadow-lg header-img" alt="Example image" width="700" height="500" loading="lazy">
         </div>
         <div class="d-grid gap-2 d-sm-flex justify-content-sm-center mb-5">
           <a href="#submit_payment" type="button" class="btn btn-primary me-sm-3 payment-btn">Primary button</a>
         </div>
       </div>
     </div>
   </section>

   <!-- the problem -->
   <section class="section-style">
     <div class="container col-xxl-7 px-4 py-5">
       <h1 class="display-6 fw-bold header-style">Stop wasting time on strategies that don't work!</h1>
       <div class="row flex-lg-row-reverse align-items-center g-5 py-5">
         <p class="body-paragraph poppins-medium">Deep down you feel <i>restless and frustrated</i> because <u>you know</u> you're made for more.</p>
         <p class="body-paragraph">You work really hard to <b><i>grow other people's wealth.</i></b> And in return for your hard work, your income potential is controlled by someone else.</p>
       </div>
     </div>
   </section>

   <!-- the solution -->
   <section class="bk-rose section-style">
     <div class="container col-xxl-7 px-4 py-5">
       <h1 class="display-6 fw-bold">You can copy my success!</h1>
       <div class="row flex-lg-row-reverse align-items-center g-5 py-5">
         <p class="body-paragraph poppins-medium">I'm going to reveal EXACTLY how I did it!</p>
         <p class="body-paragraph">How I quit trading hours-for-dollars in my corporate job.</p>
         <h2 class="pull-quote">You can copy my success! Imagine…</h2>
       </div>

       <div class="inline-flex-item body-paragraph poppins-medium">
         <div class="vertical-align">
           <i class="fas fa-check fa-2x"></i>
         </div>
         <div class="list-text">
           <p>Blowing the cap off of your income goals and having a <u>limitless</u> income potential.</p>
         </div>
       </div>

       <div class="inline-flex-item body-paragraph poppins-medium">
         <div class="vertical-align">
           <i class="fas fa-check fa-2x"></i>
         </div>
         <div class="list-text">
           <p>Making money with your <u>passion or skill,</u> without having to actually trade hours for dollars.</p>
         </div>
       </div>

       <div class="inline-flex-item body-paragraph poppins-medium">
         <div class="vertical-align">
           <i class="fas fa-check fa-2x"></i>
         </div>
         <div class="list-text">
           <p>Finally being able to pay off your debt, and <u>spend more time</u> with your friends and family while working from home.</p>
         </div>
       </div>

     </div>
   </section>

   <!-- testimonies -->
   <section class="testimonies section-style">
     <h1 class="display-6 fw-bold header-style">Checkout what they're saying about us!</h1>
     <div class="row">
       <div class="col-lg-4">
         <h2>Emily Dunst</h2>
         <p>If you're on the fence about this book, I say go all in! I learned a lot from it!</p>
       </div>

       <div class="col-lg-4">
         <h2>Kevin Larrison</h2>
         <p>This was an enjoyable and informative read. Get yours now and start growing that list!</p>
       </div>

       <div class="col-lg-4">
         <h2>David Sneath</h2>
         <p>I completely recommend this ebook to everyone struggling with email marketing!</p>
       </div>

     </div>
   </section>

   <!-- payment form -->
   <section class="form-section section-style">
     <h2 class="my-4 text-center">Take the 7 day email growth challenge</h2>
     <form action="./payment.php" method="post" id="payment-form">
       <div class="form-row">
         <input type="text" name="firstname" class="form-control mb-3" placeholder="First Name" required>
         <input type="text" name="lastname" class="form-control mb-3" placeholder="Last Name" required>
         <input type="email" name="email" class="form-control mb-3" placeholder="Email Address" required>
         <div id="card-element" class="form-control">
           <!-- a Stripe Element will be inserted here. -->
         </div>
         <div id="card-errors" role="alert"></div>
       </div>

       <button id="submit_payment" class="payment-btn">Submit Payment</button>
     </form>
   </section>

 </div>

 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
 <script src="https://js.stripe.com/v3/"></script>
 <script src="./js/payment.js"></script>
</body>

</html>

The structure of the page is similar to the annotated diagram above. First, there is a container element that contains five sections:

  1. The hero section
  2. The problem section
  3. The solution section
  4. The testimonials section
  5. The payment section

The hero section

The "hero" section is the first thing your visitors will see when they land on your sales page, it contains the attention-grabbing headline and sub-headline that will excite your audience and encourage them to read further.

The problem section

The next part is the "problem" section. Here, you will identify with and explain to your customer why their current strategy is not working. This is where you tell your own story of how you struggled with the same problem your customer is struggling with and how you finally overcame the issue to find victory.

The solution section

You then follow this narrative with the "solution" section. Here, you introduce your customer to your product and show how it is the answer to the problem you identified in the "problem" section.

The social proof section

Next comes social proof. Here, you prove that you’re legitimate by showing testimonies of your past clients, or the results you’ve achieved by using your product. This part is absolutely essential to the page's final section: the payment section.

The payment section

This last part is your final opportunity to turn your visitor into a happy customer, so it’s also a good idea that you remove any doubts they may still have by offering some kind of "risk-free" guarantee just before you ask for their payment details.

This design gives you a lot of flexibility because you can easily add as many additional sections, as needed. The general rule of thumb for sales pages is that the more expensive your product, the longer your copy. However, it may need to do some A/B testing to discover the formula that works best.

Download the hero image

With the main template in place, download the hero section's hero image, from Unsplash, to the img directory, and name it email_marketing.jpg.

Add the page styling

To give the page some styling, paste the following CSS code into css/style.css.

/*
 * The header 
 */
.CTA-btn {
    margin-top: 50px;
}

.header-img {
    margin: 25px 0px 50px;
}

.header-style {
    margin-bottom: 70px;
}

/* 
 * The call to action 
 */
.payment-btn,
.btn {
    background-color: #ff0080 !important;
    border: none !important;
    padding: 20px 120px 20px !important;
    color: #fff !important;
    text-transform: uppercase;
    font-weight: bold;
    letter-spacing: 0.9px;
}

/*
 * The problem 
 */
section .container {
    padding-top: 1.5rem !important;
    padding-bottom: 1.5rem !important;
}

.body-paragraph {
    font-family: "Poppins", sans-serif;
    font-weight: 300;
    line-height: 40px;
    color: #000;
    font-size: 22px;
    letter-spacing: 0.5px;
    margin: 25px 0px 0px !important;
    padding: 10px 0px 0px;
}

.poppins-medium {
    font-weight: 500;
}

.pull-quote {
    text-align: center;
    font-size: 40px;
    color: #ff033e;
}

/*
 * The solution
 */
.bk-rose {
    background-color: #f9ede5;
}

.single-p-spacing {
    padding: 0px 24px 0px;
}

.fa-check:before {
    content: "\f00c";
    padding-right: 20px;
    color: #6b9abe;
}

.inline-flex-item {
    display: inline-flex;
    width: 100%;
}

.section-style {
    margin-bottom: 100px;
}

/* 
 * The Stripe payment form 
 */
.form-section {
    display: inline-block;
    width: 750px !important;
}

Add the required Javascript

Now, add the code for the Stripe payment, by pasting the following code into js/payment.js.

var stripe = Stripe('pk_test_ADD_YOUR_PUBLISHABLE_KEY_HERE');

var elements = stripe.elements();

var style = {
 base: {
   color: '#ff033e',
   fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
   fontSize: '16px',
   '::placeholder': {
     color: '#ff033e'
   }
 },
 invalid: {
   color: '#F22F46',
   iconColor: '#F22F46'
 }
};

document.querySelector('#payment-form button').classList =
 'btn payment-btn btn-block mt-4';

var card = elements.create('card', { style: style });
card.mount('#card-element');
card.addEventListener('change', function(event) {
 var displayError = document.getElementById('card-errors');
 if (event.error) {
   displayError.textContent = event.error.message;
 } else {
   displayError.textContent = '';
 }
});

var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
 event.preventDefault();
 stripe.createToken(card).then(function(result) {
   if (result.error) {
     var errorElement = document.getElementById('card-errors');
     errorElement.textContent = result.error.message;
   } else {
     stripeTokenHandler(result.token);
   }
 });
});

function stripeTokenHandler(token) {
 var form = document.getElementById('payment-form');
 var hiddenInput = document.createElement('input');
 hiddenInput.setAttribute('type', 'hidden');
 hiddenInput.setAttribute('name', 'stripeToken');
 hiddenInput.setAttribute('value', token.id);
 form.appendChild(hiddenInput);

 form.submit();
}

The code initializes a Stripe object with your client-side key. To get your key, go to your Stripe developer’s dashboard and switch to test mode, by enabling it in the top right-hand corner.

Retrieve Stripe Publishable key

Then, click on the Publishable key to copy it and paste it into js/payment.js, in place of pk_test_ADD_YOUR_PUBLISHABLE_KEY_HERE.

The code starts by instantiating an instance of Stripe Elements. These are:

a set of pre-built UI components for building your web checkout flow.

The document.querySelector adds some CSS classes to the button for styling it with CSS. Then, a set of custom styling is created and passed to elements.create, which is then saved to the card variable.

The card is mounted to the HTML element with the id of card-element. Card.addEventListener checks that the card is valid, otherwise it throws an error. The submission is handled and a token is passed to stripeTokenHandler, and then the form is finally submitted.

With the Javascript file created, next, create the PHP script to handle form submission, by pasting the following code into payment.php.

<?php

require_once('vendor/autoload.php');

use Twilio\Rest\Client;

\Stripe\Stripe::setApiKey('sk_test_ADD_YOUR_SECRET_KEY_HERE');

$account_sid = 'TWILIO_ACCOUNT_ID';
$auth_token = 'TWILIO_TOKEN';
$twilio_number = "TWILIO_NUMBER";
$your_phone_number = 'YOUR_PHONE_NUMBER';

// Sanitize POST Array
$POST = filter_var_array($_POST);

$first_name = $POST['first_name'];
$last_name = $POST['last_name'];
$email = $POST['email'];
$token = $POST['stripeToken'];

// Create Customer In Stripe
$customer = \Stripe\Customer::create(
    [
        "email" => $email,
        "source" => $token,
        "name" => $first_name
    ]
);

// Charge Customer
$charge_customer = \Stripe\Charge::create(
    [
        "amount" => 100,
        "currency" => "usd",
        "description" => "The 7 Day Email Bootcamp",
        "customer" => $customer->id
    ]
);

// Redirect to success
header('Location: success.php?tid=' . $charge_customer->id . '&product=' . $charge_customer->description);

// Send automated message

$client = new Client($account_sid, $auth_token);
$client->messages->create(
    $your_phone_number,
    [
        'from' => $twilio_number,
        'body' => "You've made a sale! Course name: 7 Day Email Bootcamp."
    ]
);

With the code in place, you need to replace the various placeholders. Start off by replacing 'sk_test_ADD_YOUR_SECRET_KEY_HERE' with your Stripe Secret Key. As before, you can get this from your developer’s dashboard.

Retrieve Twilio credentials

Then, open the Twilio console, retrieve your Twilio Account SID and Auth Token, and paste them in place of TWILIO_ACCOUNT_ID and TWILIO_TOKEN respectively.

Retrieve Twilio phone number

To locate your Twilio phone number, click on "Explore Products". Then, scroll to the bottom of the page, and under "Super Network" click on "Phone numbers". Then, under Manage, click "Active numbers". Copy the phone number and paste it in place of TWILIO_NUMBER.

Finally, replace the YOUR_PHONE_NUMBER placeholder with your, personal, phone number.

The code starts off by sanitizing the $_POST data with the filter_var_array() function and uses the result to initialize a new  variable, named $POST.

Then, it initializes a new Stripe Customer object, named $customer, with the customer's email, token, and first name.

To charge the customer, it creates a Stripe Charge object, named $charge_customer, with the amount, currency, description, and customer id.

With Stripe, the amount does not have a decimal point. So a value of 100 means $1.00.

Then, the header() function redirects the user upon successful submission. The first argument is the location of the file, which is success.php, followed by the customer's id, product, and product description.

Lastly, Twilio’s SMS API sends a sales notification message to your mobile number, alerting you to the sale. To do this, it uses three variables: $account_sid, $auth_token, and $twilio_number

After that, a Twilio Client object, named $client, is initialized with the Twilio Account SID and Auth Token, and sends a confirmation SMS to your phone number.

Create the success page

Now, it’s time to create the success page, the final part of the customer’s journey, by pasting the following code into success.php.

<?php
if (!empty($_GET['tid'] && !empty($_GET['product']))) {
    $GET = filter_var_array($_GET);
    $tid = $GET['tid'];
    $product = $GET['product'];
} else {
    header('Location: index.php');
}
?>

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
 <title>Thank You</title>
</head>

<body>
 <div class="container mt-4">
   <h2>Thank you for purchasing <?php echo $product; ?></h2>
   <hr>
   <p>Your order number is: <?php echo $tid; ?></p>
   <p>Check your email for the course download</p>
   <p><a href="index.php" class="btn btn-light mt-2">Go Back</a></p>
 </div>
</body>

</html>

It begins by getting the parameters from the submitted form, via the $_GET superglobal. It checks that the submission is not empty and, if not, uses filter_var_array($_GET) to filter the $_GET superglobal array.

Afterwards, the tid and product values are retrieved from the $_GET array and set to the variable $tid and $product. If tid and product are missing, the page redirects to index.php.

In the HTML section, we place the variables received with the accompanying message to the customer. Here, it thanks the customer for their purchase, giving them their order number, and directing them to where they can download the course.

That’s it! You’ve completed the sales page. It’s now time to host it on Google App Engine.

Create an instance on Google App Engine

You need to create a new project on the Google Cloud Platform. To do this, open your Google Cloud Console, click on “Select a project > New Project” and enter the project name, the organization that you wish to attach the project to, and location.

Then, click Create. After the project's created, click Select Project.

Welcome to Google Cloud

Next, you need to create a VM instance on the App Engine. To do this, click on the hamburger icon on the top left corner of your console, select App Engine, and then click on Dashboard.

Create Google Cloud VM

After that, click CREATE APPLICATION, select the region closest to you, and then click NEXT. Then, when the application's created, at the bottom of the page, click I'LL DO THIS LATER.

Google Cloud - Create Application
 

To check that an application has successfully been created, click the hamburger menu once more. Select Cloud Storage (under Storage), and then click Browser, as in the image below.

Google Cloud - Cloud Storage Browser

Two buckets should have been created for you. The bucket prefixed with the name of the project you just made will be your sales page URL.

Google Cloud Buckets List

Now, it’s time to deploy the application.

Deploying to the App Engine

Before you can deploy to App Engine, you need to configure your App Engine app’s settings. To do this, create a new file, in the project's root directory, named app.yaml. This file contains information about your app’s code, such as the runtime environment, and it also acts as a descriptor for its deployment. 

Then, paste the following code into app.yaml.

runtime: php
env: flex
runtime_config:
 document_root: .

Here, setting runtime to php sets PHP as the language runtime to use. Setting env to flex enables the App Engine's flexible environment. This will scale your app up or down automatically whilst simultaneously balancing the load. And document_root specifies the relative path from the project root directory.

Now, you need to initialize the project. Whilst you’re still in the project’s root directory, run the following commands in the terminal.

gcloud init

Follow the prompts until you complete the initialization phase, where you'll see "You are now authenticated with the gcloud CLI!".

The next thing is to deploy the app on the App Engine. To do this run the following command.

gcloud app deploy

You'll see output similar to the following in the terminal.

Services to deploy:

descriptor:                      [/opt/salespage/app.yaml]
source:                          [/opt/salespage]
target project:                  [salespage-343700]
target service:                  [default]
target version:                  [20220311t122555]
target url:                      [https://salespage-343700.oa.r.appspot.com]
target service account:          [App Engine default service account]

Do you want to continue (Y/n)?

Press Enter to accept the default answer (Y).

When the deployment has been completed — which may take a few minutes — run the command below to visit your brand new sales page.

gcloud app browse

Sales page before submission

Test the application

Fill in the payment form using the test credit card number “4242 4242 4242 4242”. Use any, random, numbers for the expiry date and the postcode. Then, submit the form.

Assuming it all works as expected, your Twilio account should populate with the customer’s details, and a text message will be sent to your phone. And, if you check your Stripe account, you'll see a payment registered, as in the image below.

Stripe payments list

Congratulations on finishing this project. You now possess a lucrative set of skills!

If you want, you can further extend this project by adding an SSL certificate to your custom domain. If you’re really ambitious, you can create signup and login features, turn it into a “minimum viable product” and invite others to use it.

The important thing to remember is that your options are limitless. Go for it, build something awesome and send me a link so that I can celebrate your success with you.

Ijeoma Nelson is a former Deloitte software and cloud engineer. She discovered a hidden passion for programming after winning Startup Weekend Silicon Valley. These days she can be found planning her travel adventures for 2022 whilst building cool stuff with code. You can reach her via LinkedIn or Twitter.