Build an Etsy Clone with Angular and Stamplay (Part 3)

Chris Sevilleja
💬 comments

In Parts 1 and 2, we did a lot of work. We managed to:

  • Set up our backend database and API
  • Designed our site
  • Allowed for Facebook and Email registration/login
  • Let users create products
  • Show off all the great products

We did all the above with AngularJS and the Stamplay JS SDK. Part 1 totally dealt with the Stamplay dashboard and setting things up there while Part 2 dealt with our front-end code.

In part 3 we'll be working on both the back-end (Stamplay) and the front-end (AngularJS code). Here are the things we'll accomplish to finalize our Etsy store:

Table of Contents

    1. Handle charging users for products using Stripe
    2. Email the product owner when their product is bought
    3. Email the user that bought the product when they buy
    4. Handle product searches using Algolia
    5. Wiring up comments for products (we already laid the groundwork for this in part 2)
    6. Email a product owner when their product is commented on
    7. View purchase history

    Luckily for us, Stamplay can help us handle a lot of the search features by talking to the Algolia API for us. They will also let us handle hooking in Stripe easily without a single line of code.

    The email notifications will also be handled from the Stamplay dashboard without any code at all thanks to Stamplays API lego/IFTTT like system.

    Let's get started with Stripe and charging users (don't worry we'll only be using Stripe's developer mode so no charges will go through).

    Charging for Products with Stripe

    First things first, let's go to Stripe and create a free account.


    Once you have that, you will find yourself at your new Stripe dashboard. Notice that by default, this account will be in Test mode. No transactions will go through.


    When normally integrating Stripe, we'd have to go grab our API keys. For future reference, they are located under your account nav in the top right, go to Account Settings. Then go to API Keys.


    With Stripe however, we don't have to go and grab those and paste them into Stamplay. Stamplay's dashboard is advanced enough to authorize through Stripe directly. With that in mind, let's go over to the Stamplay dashboard to connect Stripe.

    Under Tasks -> Components we'll see all the integrations that Stamplay can easily set up for us.


    Select Stripe and click Connect. You'll be prompted with a popup to hook in your Stripe account.


    Connect your stripe account and your credentials will automatically be populated for you!


    Live Mode is off by default here so Stamplay will use the Test Public API Key. When we want to start really charging users here, we can flip this switch and then flip the switch in our Stripe dashboard and we're good to go!

    Billing Emails

    There are a couple of things that need to happen as soon as a user buys a product:

    • Email the product owner that their product has been bought
    • Email the user confirming that they purchased a product

    Stamplay makes both of these very simple using their Tasks system. Basically all we need to do in the dashboard is set up tasks that says:

    • When a Product is purchased, email the product owner
    • When a Product is purchased, email the purchaser

    We can do all this without a line of code. Let's take a look. In the Stamplay dashboard, go to Tasks -> Components.

    We are going to need a service to handle sending emails for us. For this, we'll be using MailChimp's Mandrill service. They help make sending emails easy as we don't have to worry about setting up our own email servers, and handling that whole system.

    Let's go over to Mandrill and create an account.


    Once we have our account, you'll reach the Mandrill dashboard. We are going to need the SMTP address and an API key. We'll find those under Settings.

    I've gone ahead and created a new API Key with the Angular Etsy Stamplay Demo description.


    With that in hand, let's go back to the Stamplay dashboard and enter in our credentials. Under Tasks -> Components, click on the Mandrill logo and enter in your credentials.


    Creating Our First Tasks (Emailing a User)

    With Mandrill connected, we can create our email task. Go to Tasks -> Manage and let's create a new task.


    Click New Task and we'll see a very simple when-then interface. Remember in part 1 that we created an Order custom object. The trigger will be when an Order object is created and the action will be Send an Email. Let's configure that now:


    Next we move on to configure the trigger of this task. In our case, we want to trigger this on creation of a new order.


    The next step is to configure our transactional message through Mandrill. Per the Manrdill messages docs, we are going to send our email as a JSON object.

    Here is the format of our JSON object:

      "message": {
        "html": "<p>Thanks for your purchase of {{}}</p>",
        "subject": "Your Purchase",
        "from_email": "",
        "from_name": "Chris Sevilleja",
        "to": [
            "email": "{{}}",
            "name": "{{user.displayName}}",
            "type": "to"

    Stamplay let's us conveniently enter in fields from our trigger data (order) and the user that created the order. Just click on the fields to the right to populate that data. You can see we used that in the to fields with {{}}.

    The last step is to name this task (New Order - Customer) and be on our way!

    The Checkout System

    This will deal with the front-end side of things. We'll use Angular controllers and services to interact with our Stamplay API when a user checks out. The main things we are going to accomplish is to:

    • Charge a user through Stripe
    • Create a new order (custom Stamplay object)

    We already built the routes, views, and Angular controller out for this page in part 2, so all we have to do is fill out the view and the functionality in the controller.

    Charging a User

    To charge a user, we will need to pull in the Stripe JS SDK. Then the Stamplay JS SDK comes with a Stripe object so that we can create charges, customers, subscriptions, and more.

    We're going to add and configure the Stripe SDK by adding the following line to our index.html file:

    <script type="text/javascript" src=""></script>

    Now that we have that configured, we can create the functions to charge a customer and create an order in our OrderService.js file.

    Let's add the following to app/shares/OrderService.js:

    We have three main functions here:

    • create: This will be used at checkout to create a new order (by creating a new order, we will also fire off those email tasks we set up in the dashboard)
    • charge: This will use the Stripe SDK to create a credit card token (no credit card data ever leaves the users client browser) and then the Stamplay JS SDK will help us handle the charge by using the Stripe token.
    • history: This will let us view the purchase history of a user.
    // OrderService.js
      .module('OrderService', [])
      .factory('Order', ['$stamplay', '$q', '$http', OrderService]);
    function OrderService($stamplay, $q, $http) {
      return {
        create: create,
        charge: charge,
        history: history
       * Create a new order
      function create(data) {
        var def = $q.defer();
        // instantiate a new order model from the stamplay js sdk
        var order = new $stamplay.Cobject('orders').Model;    
        // loop over the fields in data and update the order
        angular.forEach(data, function(value, key) {
          order.set(key, value);
        // save the object
          .then(function() {
        return def.promise;
       * Charge a customer
      function charge(userID, price, cardData) {
        var def = $q.defer();
        // create the card token
        Stripe.card.createToken(cardData, function(status, response) {
          // we now have the card token
          var token =;
          // use the stamplay sdk to charge the user
          price = price * 100; // turn the price into pennies
          var customer = new $stamplay.Stripe(); 
          // charge the customer
          customer.charge(userID, token, price, 'USD')
            .then(function() {
        return def.promise;
       * View all the orders for one user
      function history(userID) {
        var def = $q.defer();
        // instantiate a new orders collection from the stamplay js sdk
        var orders = new $stamplay.Cobject('orders').Collection;
          .then(function() {
        return def.promise;

    Now we can add this to our application in the app.js file:

    // app.js
      .module('etsyApp', [

    With that added, we can now move onto our checkout controller in app/components/checkout/checkout.js:

    Recall in part 2 that we routed our Buy Now button to this checkout page using ui-sref="checkout({ id: })". This ultimately makes the checkout link look like so:

    What we're going to do is grab that product id from the url, show that product, let users enter in their user information, and then let them checkout. Real quick, we want to make sure that a user is logged in to purchase, so back in product.html, we're going to conditionally show the Buy Now button or a Login/Signup button.

    Just replace the Buy Now code with the following:

    <!-- buy now button links to checkout route -->
    <!-- only show buy now button if logged in -->
    <a ui-sref="checkout({ id: })" class="listing-buy btn btn-success btn-block" ng-show="">
      Buy Now
    <a ui-sref="authenticate" class="listing-buy btn btn-success btn-block" ng-show="!">
      Login/Signup to Purchase

    Once again, we are utilizing ng-show to conditionally show/hide things.

    Now if a user is logged in, they'll be able to click through to the checkout page.

    The Checkout Process

    Let's handle all the checkout logic now in checkout.js:

    // checkout.js
      .module('app.checkout', [])
      .controller('CheckoutController', ['$stateParams', '$rootScope', 'Product', 'Order', CheckoutController]);
    function CheckoutController($stateParams, $rootScope, Product, Order) {
      var checkout             = this;
      checkout.orderData       = {};    // create an empty object to hold order data
      checkout.cardData        = {};    // create an empty object to hold credit card data
      checkout.processPurchase = processPurchase;
      // grab the product by the $
        .then(function(data) {
          // since this is a singular Stamplay model that was returned, we can bind instance directly
          checkout.product  = data.instance;
          // grab the product id and set it to an object called orderData
          checkout.orderData.product = [data.get('_id')];
          checkout.orderData.price    = data.get('price');
       * Process the purchase
      function processPurchase() {
        // clear the success message
        checkout.sucessMessage = '';
        // charge the user first
        Order.charge($, checkout.orderData.price, checkout.cardData)
          .then(function(data) {
            // then we will create the order on successful charge
              .then(function(data) {
                // purchase successful
                checkout.successMessage = 'Thanks for your order! Your order number is #' + data.get('_id');

    One landing on the checkout page, we are grabbing the product that is being purchased using Product.get($ We'll then bind that to checkout.product.

    We'll also start building out our orderData object here by passing in the price and the product ID into the price and product fields.

    Then we'll create a processPurchase() function to handle the checkout form. This function is responsible for charging a user's credit card and then creating an order. We're calling the functions we created in OrderService.js: Order.charge() and Order.create().

    With all that wired up, let's move to the view, checkout.html.

    <!-- checkout.html -->
    <div class="checkout-page">
      <div class="page-header text-center">
        <h1>Purchase {{ }}</h1>
      <div class="row">
      <div class="col-sm-8 col-sm-offset-2 col-md-4 col-md-offset-4">
        <form ng-submit="checkout.processPurchase()" novalidate>
          <!-- BASIC PRODUCT INFO -->
          <h4>Basic Info</h4>
          <div class="form-group">
            <p><strong>Product:</strong> {{ }}</p>
            <p><strong>Price:</strong> {{ checkout.product.price | currency }}</p>
          <div class="form-group" ng-show="checkout.product.color">
            <select class="form-control" ng-model="checkout.orderData.color" 
              ng-options="color for color in checkout.product.color">         
          <div class="form-group" ng-show="checkout.product.size">
            <select class="form-control" ng-model="checkout.orderData.size" 
              ng-options="size for size in checkout.product.size">          
          <!-- BILLING INFO -->
          <h4>Billing Info</h4>
          <!-- CARDHOLDERS NAME -->
          <div class="form-group">
            <label>Cardholder Name</label>
            <input type="text" class="form-control">
          <!-- CREDIT CARD NUMBER -->
          <div class="form-group">
            <label>Card Number</label>
            <input type="text" class="form-control" placeholder="4242 4242 4242 4242" ng-model="checkout.cardData.number">
          <!-- CVC AND EXPIRATION -->
          <div class="row">
            <div class="col-sm-6">
              <div class="form-group">
                <label>Card CVC</label>
                <input type="text" class="form-control" placeholder="123" ng-model="checkout.cardData.cvc">
            <div class="col-sm-6">
              <div class="form-group">
                <label>Card Expiration</label>
                <div class="row">
                  <div class="col-sm-6">
                    <input type="text" class="form-control" placeholder="08" ng-model="checkout.cardData.exp_month">
                  <div class="col-sm-6">
                    <input type="text" class="form-control" placeholder="2020" ng-model="checkout.cardData.exp_year">
          <!-- SUCCESS MESSAGE -->
          <div class="alert alert-success text-center" ng-show="checkout.successMessage">
            {{ checkout.successMessage }} 
          <!-- CHECKOUT BUTTON -->
          <button type="submit" class="btn btn-success btn-block">Checkout</button>

    Now our page will look like the following:


    Fill out the form and use the following for your credit card info (Stripe let's us use these test credentials):

    Name: Anything
    Card Number: 4242 4242 4242 4242
    CVC: 123
    Expiration: Any date in the future (08/2020)

    Now when we checkout, we should see everything go according to plan.


    And if we go into our Stamplay dashboard, we'll see the new order created under Data -> Objects -> Orders:


    We can also go over to our Stripe dashboard and see the charge (there are a few here since I tested three times)


    We're all good now! Charging for products is done. Didn't even have to go digging through the Stripe API to see how to deal with charges through them. The Stamplay SDK made that easy for us.

    Showing Order History

    Let's add purchase history to the Admin section of our site. We already have that capability in the OrderService.js so all we need to do is inject that into admin.js and our AdminController and then grab purchase history.

    Let's inject it now:

    // admin.js
      .module('app.admin', [])
      .controller('AdminController', ['Product', 'Order', AdminController]);

    Now we can use it in the controller:

    // admin.js
    function AdminController(Product, Order) {
       * Get all the orders
        .then(function(data) {
          admin.orders = data.instance;

    We've gotten all the orders and bound them to the admin.orders object. Now we just have to display them in admin.html:

    <!-- admin.html -->
    <div class="col-sm-6">
    <div class="well">
      <!-- create a product form -->
      <div class="page-header text-center">
        <h3>Order History</h3>
      <table class="table table-striped table-hover table-bordered">
            <th>Order #</th>
          <tr ng-repeat="order in admin.orders">
            <td>{{ order.instance._id }}</td>
            <td>{{ order.instance.product[0].name }}</td>
            <td>{{ order.instance.price | currency }}</td>

    And just like that, we can see order history! There was a lot of setup in the beginning of our application with the whole components directory and all the services and controllers, but once we have the overall layout of our application good, adding features is a simple task since everything is so organized.


    Searching for Products

    Just like we did with Stripe integration, we are going to integrate Algolia into our application.


    They have a very neat service and handle all the intricacies of search engine technology for us. Once you create your account you'll be shown a clear tutorial, but just go ahead and skip it. Those are if you want to import your own data already.


    We currently don't have any so we'll move forward.

    Creating an Index

    An index will be Algolia's way of keeping certain items ready to be searchable. In our case, products.

    Click on Indices on the left nav and let's create a product index.


    Just enter products as our new index and we're good to go. Stamplay will handle all the other heavy duty work for us like adding a new product to our index whenever a new product is created.

    How will Stamplay do this? With a task of course! Let's go connect Algolia and create that task now.

    Stamplay and Algolia Integration

    Grab your Algolia API credentials under the Credentials nav item. We'll want the Application ID and Admin API Key here.


    In the Stamplay dashboard, let's connect Algolia under Tasks -> Components


    Now we can create the Stamplay task to add new products to the Algolia index. Under Tasks -> Manage -> New, let's create a new task:


    When a new product is created, then we will post data to Algolia. We're going to pass information to Algolia as new lines separated by a |. This is what we'll be passing over to Algolia:

    id | {{coinstance._id}}
    name | {{}}
    price | {{coinstance.price}}
    size | {{coinstance.size}}
    description | {{coinstance.description}}
    category | {{coinstance.category}}
    color | {{coinstance.color}}  


    We'll call this task Add Product -> Algolia.


    Any products added so far will not be indexable by Algolia, but you can create another task to add them to Algolia if a product is updated. You can also create a task to update the Algolia index if a new product let's say gets voted up so users can search via ratings.

    Let's create a sample product using the API Console in the Stamplay dashboard and see this product get added to Algolia.


    We can go visit our Algolia dashboard and click on Indices and then Products. We'll see our newly created product there:


    By default, Algolia let's all attributes be searchable and you can set that under the Ranking tab under Indices. For our demo purposes, we'll keep all attributes indexable. You may want to specify certain indices in your applications as it helps Algolia narrow down searches as fast as possible.

    You can go ahead and type into the search bar found under the Browse tab and see Algolia do a real-time search. Let's integrate this great feature into our own site now.

    Adding a Typeahead Search Bar

    We already have search bar in the header of our Etsy clone, but we haven't done anything with it yet.

    To integrate Algolia, we're going to use Bower again to call in the Algolia Search API and UI Bootstrap for its typeahead directive (we want search results to show up as we type).

    For more on UI Bootstrap read: How to Correctly Use AngularJS and Bootstrap Together

    Let's install both of these with:

    $ bower install --save algoliasearch angular-bootstrap

    Now we can add the <script> to our index.html file:

    <!-- index.html -->
    <script src="./bower_components/algoliasearch/dist/algoliasearch.angular.js"></script>
    <script src="./bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js"></script>

    We are loading the Angular specific version here so we'll also have to inject the module into our app.js file:

    // app.js
      .module('etsyApp', [

    Since the search bar is available throughout our entire application, we'll be handling this functionality inside of the MainController inside the app.js file.

    Now that we have loaded the Algolia SDK and injected the module, we have access to an algolia object inside of our application. We're going to add that and a couple other things we need ($q and $state) to the MainController:

    // app.js
    .controller('MainController', ['User', '$rootScope', 'algolia', '$q', '$state', MainController]);
     * The main controller for our application
    function MainController(User, $rootScope, algolia) {
      // configure algolia
      // grab our credentials from algolia
      var client = algolia.Client('4KRGXPTF7K', '4594f3b07157188f25b3f5a8a7eba04e');
      var index = client.initIndex('products');

    That's it for configuring Algolia. It's important to note that the API key used here is the Search-Only API Key. This API Key only has access to search unlike the Admin API Key that we added to the Stamplay dashboard which has permissions to actually add products to our Algolia index.

    With that ready to go, let's create a function to handle searching for products and also a function for what to do when we click on a product that we've searched for.

    // app.js
    function MainController(User, $rootScope, algolia, $q, $state) {
      main.searchProducts = searchProducts;
      main.searchPicked   = searchPicked;
       * Use algolia to search for products
       * The typeahead function uses promises which is why we use $q
      function searchProducts(query) {
        var def = $q.defer();
        // do the search, { hitsPerPage: 10 })
          .then(function(data) {
            // return the found items
          }, function(content) {
            // return no items
        return def.promise;
       * What to do when an item from the search box is clicked
       * Navigate to that product using ui-routers $state.go
      function searchPicked($item, $model, $label) {
        $state.go('product', { id: $, name: $ });

    Per the UI Bootstrap Typeahead docs, we can use the typeahead directive on our input search bar. Every time a user types into our search box, we are going to call the searchProducts() function.

    When a user clicks on a found product, we'll use the searchPicked() function. The typeahead directive gives us access to the $item, $model, and $label objects automatically so we'll use the id to route users to the product they select.

    Here's the HTML for our new typeahead input box:

    <!-- index.html -->
    <!-- search form -->
    <form class="search-form navbar-form navbar-left">
      <div class="form-group">
      <div class="input-group">
          placeholder="Find a Product"
          typeahead="product as for product in main.searchProducts($viewValue)"
          typeahead-on-select="main.searchPicked($item, $model, $label)">
          <div class="input-group-addon">
            <span class="glyphicon glyphicon-search"></span>

    Now we have our new search input box:


    And when we start typing into it, we can see results returned from Algolia! Currently we only have the Das Keyboard in there so type in a da and you'll see the result.


    Note: We have to add a little CSS so that our menu stylings don't make the text white as we hover. You'd probably want to be more specific with your stylings so that the dropdowns don't turn white on hover, but for now we'll add this simple CSS line to override.

    /* style.css */
    /* search form ------------------------------------------*/
    .search-form a:hover  { color:#333 !important; }

    Click on the product that shows up in the dropdown and we'll navigate to that page now. Searching is all good now powered by Algolia so you know that it is highly scalable and flexible.

    Commenting on Products

    The last thing we'll do to finalize our AngularJS Etsy Clone is to allow users to comment on products. We already built in the functionality for this into the ProductService.js service so we just need to implement it in the ProductController in the product.js file.

    Before we do that however, let's take care of the easy part. We want to email the owner of a product that they have received a new comment. We can do that with a Stamplay task.


    We'll configure the trigger to be a comment on a product. Then we'll use Mandrill to send a message to the product owner. Here is the JSON for that task:

      "message": {
        "html": "<p>You have a new comment on your product: {{}}</p>",
        "subject": "New Comment",
        "from_email": "",
        "from_name": "Chris Sevilleja",
        "to": [
            "email": "{{}}",
            "name": "{{coinstance.owner.displayname}}",
            "type": "to"


    With that out of the way, we are now going to handle adding the actual comments on the product page. On the ProductService.js, we already have the ability to create a comment with the comment() function. Now we'll just wire those into the ProductController (app/components/product/product.js):

    // product.js
    function ProductController(Product, $stateParams) {
      var product           = this;
      product.createComment = createComment;
       * Create a new comment on this product
      function createComment() {
        Product.comment($, product.commentData)
          .then(function(data) {
            // clear the comment form
            product.commentData = {};
            // replace the comments with the new comments returned
            product.listing.actions.comments = data.instance.actions.comments;

    We've already wired up showing comments in product.html and now we have the ability to create a comment. Next up is to wire up the HTML in product.html to show the comment form to create comments.

    <!-- product.html -->
    <!-- the comment form -->
    <form ng-submit="product.createComment()" class="create-comment">
      <div class="form-group">
        <input type="text" class="form-control" placeholder="What are you thinking?" ng-model="product.commentData.text">
      <div class="text-right">
        <button type="submit" class="btn btn-primary">Comment</button>

    Now that form is wired up and will call the createComment() function on the ProductController. Let's revamp our comment listing a little bit to add some much needed stylings:

    <!-- product.html -->
    <!-- repeat over the comments -->
    <div class="listing-comments">
      <div class="comment clearfix" ng-repeat="comment in product.listing.actions.comments | orderBy:'-dt_create'">
        <!-- show an avatar if it exists or a placeholder image -->
        <div class="comment-avatar pull-left">
          <div ng-show="comment.picture">
            <img ng-src="{{ comment.picture }}" class="img-responsive img-circle">
          <div ng-show="!comment.picture">
            <img src="" class="img-responsive img-circle">
        <div class="comment-content">
          <strong>{{ comment.displayName }}</strong>
          <p>{{ comment.text }}</p>

    We're just going to loop over the comments like before with a little more HTML. We're also adding the users avatar (or a bear picture if they don't have an avatar). Another thing we're going to do is orderBy the dt_create field so that the newest comments are brought to the top.

    And some CSS styling to replace our old comment CSS:

    /* style.css */
    .listing-comments .comment  {
      border-bottom:1px solid #ECECEC;  
    .comment .comment-avatar  {
    .comment .comment-avatar img  {
      margin:0 auto 10px;

    Now our comments will show up on our product page!



    That concludes our three part tutorial on building an AngularJS Etsy Clone with Stamplay. Using Stamplay we've been able to:

    • Create our database
    • Server-side API
    • Set up a solid foundation for an Angular application
    • Integrate Facebook and email logins
    • Integrate Stripe to charge users
    • Integrated emails through Mandrill
    • Integrate Algolia to get real-time searching
    • Allow users to create products
    • Upload images through Angular
    • Did some Etsy styling

    There were many techniques used in these tutorials so I hope that you were able to take away some new things learned that you can use in your own projects.

    If you are looking to get an idea or app off the ground quickly and have the stability of a tested backend system, then Stamplay is a great place to start. They can handle so many of the integrations and a lot of the parts of an application that can be time consuming and tedious.

    Let Stamplay focus on the backend and API integrations and focus on the things you really want to focus on like design, user experience, and other consumer facing parts of your application.

    This content is sponsored via Syndicate Ads.

    Chris Sevilleja

    173 posts

    Founder of Google Developer Expert in Web Technologies. Slapping the keyboard until something good happens.