JK Shop - Best eCommerce based on OctoberCMS / Laravel

Getting Started

Welcome

Welcome in documentation for JKShop eCommerce plugin for OctoberCMS based on Laravel

Benefits

  • Modern platform - OctoberCMS based on Laravel
  • Many properties on products
  • Shipping, Taxes integration
  • Generate Invoice from HTML template
  • Automatic send emails after change order status
  • Many components for easy use
  • Easy Customization

Payment Gateways

  • Paypal
  • Stripe

Live Demo

You can visit Live Demo site. You can create order, pay order by paypal (using sandbox). Or you can visit Backend and visit all sections (products, orders, categories, taxes, etc..)

Instalation

JKShop is standard plugin for OctoberCMS.

* recommended


General information

Categories

Tree of categories is most important for correct setup products.

  • Title & Slug - use good words for friendly URL
  • Sales - If you have installed RainLab User plugin you can set sale for current category and current user.

Brands

If you setup brand correctly you can filter products by brand.

  • Title & Slug - use good words for friendly URL

Taxes

Taxes when you can use fro products, shipping, etc..

Carriers

System have to at least one active carrier. Important for correct setup is section Pricing:

  • Free Shipping - on / off
  • Tax - all prices here are without tax
  • Billing: According to total weight OR According to total price

Order Statuses

Order statuses working with OctoberCMS mail templates. After instalation you have available this statuses with email templates. But you have to set created mail templates into statuses.

When Order change status (manual change from backend or automatic change - payment accepted paypal) it is generate event - sent email if is setup.

Orders can be filter by status.

Products

Products have to many fields. Sections:

  • Detail - general information
  • Prices - if you have user plugin you can set individual price for current user
  • SEO
  • Categories - check all categories from root to current
  • Size - size of package
  • Quantities
  • Customization - can be add custom fields( label; value )
  • Properties - you can select properties for this product (size, color, etc..)
  • Accessories - select accessories (products)
  • Featured - select featured products
  • Images - list of image (first image use as primary image)
  • Attachments - list of files (fill title file)

Orders

List of orders, you have full control on orders - edit, new, cancel, delete. Sections:

  • Detail - status, carrier, invoice
  • Products - product, quantity, price
  • Customer - addresses, email, phone
  • Prices - total prices
  • Payment methods - Payment method, paid date, payment detail - for paypal only

Properties

Properties extend products. Available property types:

  • Select - list of options
  • Select multiple - list of options with multiple select
  • Textbox - simple text input
  • Number - simple number input
  • Checkbox - simple checkbox input

Final result - product detail - frontend

Setting

In settings are this sections:

  • General - number formating, company email
  • Cash on delivery
  • Bank transfer
  • Paypal
  • Invoice template

Other Plugin Support

RainLab User

  • User Orders
  • User should have sale on category - in percent
  • User should have individual price on product

RainLab Pages

  • Menu - allow generate dynamic menu by categories
  • Menu - allow generate dynamic menu by brands

RainLab Sitemap

  • Categories
  • Brands
  • Products

Components

Products by Category

Display list of products by category. Parameters:

  • URL Slug Category
  • URL Page
  • Products per page
  • Product detail page

Default code

{% set index = 0 %}
    {% for product in productsPagination.items()|slice(productsPagination.perPage()*(productsPagination.currentPage()-1), productsPagination.perPage()) %}
        {# start row for each 4 products #}
        {% if index==0 %}
            <div class="row">
        {% endif %}
        {% set index = index+1 %}
        
    
        <div class="col-sm-3">
            <h3 class="cursor-pointer" onclick="location.href = '{{ product.url}}'">{{ product.title }}</h3>
            <div class="product-description">{{ product.short_description ? product.short_description|raw : product.description|raw }}</div>
            <div class="text-center">
                {% if product.images|length > 0 %}
                <img src="{{ product.images[0].thumb(auto,150) }}" class="img-responsive cursor-pointer" onclick="location.href = '{{ product.url}}'" >
                {% endif %}
            </div>
            <h4>
            {{ product.getFinalPriceFormated }}
            </h4>
            <div>
                {% if product.isAllowOrder %}
                    <button type="button" class="btn btn-primary" data-request-data="id: {{ product.id }}" data-request="onAddToBasket"><span class="glyphicon glyphicon-shopping-cart" aria-hidden="true"></span> Add to Cart</button>
                {% endif %}
                <button type="button" class="btn btn-default" onclick="location.href = '{{ product.url}}'">Detail</button>
            </div>
        </div>
        
        {# end row for each 4 products #}
        {% if index==4 %}
            {% set index = 0 %}
            </div>
        {% endif %}
    {% else %}
        <div class="no-data">no products</div>
    {% endfor %}
    
        {# if we have on page less then 4 products #}
        {% if index!=0 %}
            {% set index = 0 %}
            </div>
        {% endif %}
    
    
{% if productsPagination.lastPage > 1 %}
<div class="pagination-wrapper">
    <ul class="pagination">
        {% if productsPagination.currentPage > 1 %}
            <li><a href="{{ this.page.baseFileName|page({ (pageParam): (productsPagination.currentPage-1) }) }}">&larr; Prev</a></li>
        {% endif %}

        {% for page in 1..productsPagination.lastPage %}
            <li class="{{ productsPagination.currentPage == page ? 'active' : null }}">
                <a href="{{ this.page.baseFileName|page({ (pageParam): page }) }}">{{ page }}</a>
            </li>
        {% endfor %}

        {% if productsPagination.lastPage > productsPagination.currentPage %}
            <li><a href="{{ this.page.baseFileName|page({ (pageParam): (productsPagination.currentPage+1) }) }}">Next &rarr;</a></li>
        {% endif %}
    </ul>
</div>
{% endif %}

Products by Brand

Display list of products by Brand. Parameters:

  • URL Slug Brand
  • URL Page
  • Products per page
  • Product detail page

Default code is same for all list of products components

Products List

Display list of products, for example products on sale, etc.. Parameters:

  • Limit of products
  • Order By
  • Sort ASC or DESC
  • On Sale Only
  • Product detail page

Default code is same for all list of products components

Product Detail

Display detail of product. Parameters:

  • URL Slug
  • Brand page - List of products by brand

Default code

<div class="product-detail">
{% if not product %}
    <div class="row">
        <div class="col-sm-12">
            Product not found
        </div>
    </div>

{% else %}


    <div class="row product-detail-left">
        <div class="col-sm-5">
            {% if product.images|length > 0 %}
            <div class="product-detail-main-image">
                <img src="{{ product.images[0].thumb(auto,350) }}" class="img-responsive cursor-pointer" >
            </div>
            {% endif %}
            <div class="product-detail-list-of-images">
                {% for image in product.images %}
                <a href="{{ image.path }}" rel="galery1">
                    <img src="{{ image.thumb(60,60) }}" class="cursor-pointer" >
                </a>
                {% endfor %}
            </div>
        </div>

        <div class="col-sm-7 product-detail-right">
            <h1>
                {{ product.title }}
            </h1>
            <div class="product-detail-description">
                {{ product.description|raw }}
            </div>
            
            {% if product.customization|length > 0 %}
            <div>
                {% for custField in product.customization %}
                <div>
                    {{ custField.label }}: <strong>{{ custField.value }}</strong>
                </div>
                {% endfor %}
            </div>
            {% endif %}
            
            {% if product.brand %}
            <div>
                <div>
                    Brand: 
                    {% if (product.brand.url) %}
                    <strong><a href="{{product.brand.url}}">{{ product.brand.title }}</a></strong>
                    {% else %}
                    <strong>{{ product.brand.title }}</strong>
                    {% endif %}
                    
                </div>
            </div>
            {% endif %}            
            
            <h4>
                {{ product.getFinalPriceFormated }}
            </h4>
            <div class="text-uppercase row-border">
                {% if product.on_sale %}
                <span class="label label-success">On Sale</span>
                {% endif %}
                
                
                {% if (product.quantity / product.minimum_quantity > 1) %}
                    <span class="label label-success">In stock</span>
                {% else %}
                    <span class="label label-danger">Sold out</span>
                    <smal>{{ product.availability_date ? '(availability '~product.availability_date|date("d.m.Y")~')' : '' }}</smal>
                {% endif %}
                
                <span class="label label-default">{{ product.getConditionLabel }}</span>                
            </div>
            <div>
                {% if product.isAllowOrder %}
                    <button type="button" class="btn btn-primary" data-request-data="id: {{ product.id }}" data-request="onAddToBasket"><span class="glyphicon glyphicon-shopping-cart" aria-hidden="true"></span> Add to Cart</button>
                {% endif %}
            </div>

        </div>

        <div class="clearfix"></div>
    </div>

    {% if product.attachments|length > 0 %}
    <div class="row">
        <div class="col-sm-12">
            <h3>Attachments</h3>
            {% for file in product.attachments %}
            <a href="{{ file.path }}" target="_blank">{{ file.title ?  file.title : file.file_name }}</a><br />
            {% endfor %}
        </div>

        <div class="clearfix"></div>    
    </div>
    {% endif  %}



    {% if product.accessories|length > 0 %}
    <div class="row">
        <div class="col-sm-12">
            <h3>Accessories</h3>
            <div class="row">

                {% for accessoriesProduct in product.accessories %}
                <div class="col-sm-3">
                    <h4 class="cursor-pointer" onclick="location.href = '{{ accessoriesProduct.url}}'">{{ accessoriesProduct.title }}</h4>
                    <div class="product-description">{{ accessoriesProduct.short_description ? accessoriesProduct.short_description|raw : accessoriesProduct.description|raw }}</div>
                    <div class="text-center">
                        {% if accessoriesProduct.images|length > 0 %}
                        <img src="{{ accessoriesProduct.images[0].thumb(auto,150) }}" class="img-responsive cursor-pointer" onclick="location.href = '{{ accessoriesProduct.url}}'" >
                        {% endif %}
                    </div>
                    <h4>
                        {{ accessoriesProduct.getFinalPriceFormated }}
                    </h4>
                </div>        
                {% endfor %}

            </div>
        </div>

        <div class="clearfix"></div>    
    </div>
    {% endif  %}

    {% if product.featured|length > 0 %}
    <div class="row">
        <div class="col-sm-12">
            <h3>Featured Products</h3>
            <div class="row">

                {% for featuredOProduct in product.featured %}
                    <div class="col-sm-3">
                        <h4 class="cursor-pointer" onclick="location.href = '{{ featuredOProduct.url}}'">{{ featuredOProduct.title }}</h4>
                        <div class="product-description">{{ featuredOProduct.short_description ? featuredOProduct.short_description|raw : featuredOProduct.description|raw }}</div>
                        <div class="text-center">
                            {% if featuredOProduct.images|length > 0 %}
                            <img src="{{ featuredOProduct.images[0].thumb(auto,150) }}" class="img-responsive cursor-pointer" onclick="location.href = '{{ featuredOProduct.url}}'" >
                            {% endif %}
                        </div>
                        <h4>
                            {{ featuredOProduct.getFinalPriceFormated }}
                        </h4>
                    </div>        
                {% endfor %}

            </div>
        </div>

        <div class="clearfix"></div>    
    </div>
    {% endif  %}

{% endif %}
</div>

My Orders (Registered user)

Display list of my orders. You have to installed RainLab User plugin. Parameters:

  • URL Page number
  • Orders per page
  • My Order Detail Page

Default code

{% if rainLabPluginMissing %}
<div>
    Firstly install Rainlab User plugin
</div>
{% else %}

    <div class="table-wrapper">
        <div class="table-title-row">
            <div class="col-sm-3">Order No.</div>
            <div class="col-sm-3">Date</div>
            <div class="col-sm-3">Price incl. VAT</div>
            <div class="col-sm-3">Invoice</div>
            <div class="clearfix"></div>
        </div>
        {% for order in ordersPagination.items()|slice(ordersPagination.perPage()*(ordersPagination.currentPage()-1), ordersPagination.perPage()) %}

            <div class="table-data-row">
                <div class="col-sm-3"><a href="{{ order.url }}">{{ order.id }}</a></div>
                <div class="col-sm-3">{{ order.created_at|date("d/m/Y") }}</div>
                <div class="col-sm-3">{{ jkshopSetting.getPriceFormatted(order.total_price) }}</div>
                <div class="col-sm-3">
                    {% if (order.invoice) %}
                    <a href = '{{order.invoice.path}}' target='_blank'>download</a>
                    {% else %}
                    no invoice
                    {% endif %}
                </div>
                <div class="clearfix"></div>
            </div>
        {% else %}
            <div class="no-data">no orders</div>
        {% endfor %}
        <div class="clearfix"></div>
    </div>    
    
    {% if ordersPagination.lastPage > 1 %}
    <div class="pagination-wrapper">
        <ul class="pagination">
            {% if ordersPagination.currentPage > 1 %}
                <li><a href="{{ this.page.baseFileName|page({ (pageParam): (ordersPagination.currentPage-1) }) }}">&larr; Prev</a></li>
            {% endif %}

            {% for page in 1..ordersPagination.lastPage %}
                <li class="{{ ordersPagination.currentPage == page ? 'active' : null }}">
                    <a href="{{ this.page.baseFileName|page({ (pageParam): page }) }}">{{ page }}</a>
                </li>
            {% endfor %}

            {% if ordersPagination.lastPage > ordersPagination.currentPage %}
                <li><a href="{{ this.page.baseFileName|page({ (pageParam): (ordersPagination.currentPage+1) }) }}">Next &rarr;</a></li>
            {% endif %}
        </ul>
    </div>
    {% endif %}

{% endif %}

My Order Detail (Registered user)

Display detail of my order. Parameters:

  • URL Order ID
  • Product detail page

Default code

{% if rainLabPluginMissing %}
    <h2>
        Firstly install Rainlab User plugin
    </h2>
{% else %}
    <h2>
        Order No. {{ order.id }}
        <span class="order-status" style="background-color: {{order.orderstatus.color}};">{{order.orderstatus.title }}</span>
    </h2>
    <div class="row">
        <div class="col-sm-1">Invoice</div>
        <div class="col-sm-2">{{ order.invoice ? "<a href = '"~order.invoice.path~"' target='_blank'>"~order.invoice.title~"</a>" : "no invoice" }}</div>
        <div class="clearfix"></div>
         
        <div class="col-sm-1">Payment</div>
        <div class="col-sm-2">{{ order.getPaymentMethodLabel }}</div>
        <div class="clearfix"></div>

        <div class="col-sm-1">Date</div>
        <div class="col-sm-2">{{ order.created_at ? order.created_at|date("d/m/Y") : "" }}</div>
        <div class="clearfix"></div>

        <div class="col-sm-1">Carrier</div>
        <div class="col-sm-2">{{ order.carrier ? order.carrier.title : "" }}</div>
        <div class="clearfix"></div>
    </div>
    
    <div class="row">
        <div class="col-sm-6">
            <h4>Delivery address</h4>
            <div>
                {{ order.ds_first_name }} {{ order.ds_last_name }}<br>
                {{ order.ds_address }}<br>
                {{ order.ds_address_2 }}<br>
                {{ order.ds_postcode }}<br>
                {{ order.ds_city }}<br>
                {{ order.ds_country }}<br>
            </div>
            
        </div>
        
        <div class="col-sm-6">
            <h4>Invoice address</h4>
            <div>
                {{ order.is_first_name }} {{ order.is_last_name }}<br>
                {{ order.is_address }}<br>
                {{ order.is_address_2 }}<br>
                {{ order.is_postcode }}<br>
                {{ order.is_city }}<br>
                {{ order.is_country }}<br>
            </div>
            
        </div>
        <div class="clearfix"></div>
    </div>

    <h4>Products</h4>
    <div class="table-wrapper">
        <div class="table-title-row">
            <div class="col-sm-7">Goods</div>
            <div class="col-sm-2">Qty</div>
            <div class="col-sm-3">Price incl. VAT</div>
            <div class="clearfix"></div>
        </div>
        {% for product in order.extend_products_json  %}

            <div class="table-data-row">
                <div class="col-sm-7">
                    {% if (product.product) %}
                        <img src="{{ product.product.images|length > 0 ? product.product.images[0].thumb(60,60) }}" alt="" onclick="location.href = '{{ product.product.url }}'">
                        &nbsp;&nbsp;
                        <a href="{{ product.product.url }}">
                            {{ product.product.title }}
                        </a>
                    {% else %}
                        (no product data)
                    {% endif %}
                </div>
                <div class="col-sm-2">{{ product.quantity }}</div>
                <div class="col-sm-3">{{ jkshopSetting.getPriceFormatted(product.total_price) }}</div>
                <div class="clearfix"></div>
            </div>
        {% else %}
            <div class="no-data">no products</div>
        {% endfor %}
        <div class="clearfix"></div>
    </div>
    
    <div class="row">
        <div class="col-sm-2 col-sm-offset-9 text-right">Price excl. VAT</div>
        <div class="col-sm-1">{{ jkshopSetting.getPriceFormatted(order.total_price_without_tax) }}</div>
        <div class="clearfix"></div>
        
        <div class="col-sm-2 col-sm-offset-9 text-right">VAT</div>
        <div class="col-sm-1">{{ jkshopSetting.getPriceFormatted(order.total_tax) }}</div>
        <div class="clearfix"></div>
        
        <div class="col-sm-2 col-sm-offset-9 text-right">Price incl. VAT</div>
        <div class="col-sm-1">{{ jkshopSetting.getPriceFormatted(order.total_price) }}</div>
        <div class="clearfix"></div>
    </div>
   
{% endif %}

Basket

Basket is main component make all operation from add product to basket to finish (pay) order. The best solution is have it on the layout.

All operation is based on Ajax. Parameters:

  • ID Element Total Cart Price - This Element will be refresh after Ajax call - add product, etc.. (start with #)
  • ID Element Wrapper Basket Component - This Element will be refresh after ajax call - change quantity, etc.. (start with #)
  • Product detail page

Basket have 4 steps (views) and this steps are changed via Ajax call. If you want change components view, you have to implement your own Ajax controller.

Default Controllers

  • onRunBasket - show first page on basket
  • onRunBasketShippingPayment - show page for select shipment and payment methods
  • onRunBasketAddress - show page for add billing and delivery address
  • onRunBasketSummary - show summary page
  • onCompleteOrder - show thank you page

For developers - if you can use basket model, you can call getSessionBasket() method. All views for basket you can find here: /jkshop/components/partials/