You are on page 1of 96

A

MINOR PROJECT REPORT


ON
E-COMMERCE WEBSITE
“MEENA’S KITCHEN”

Submitted in partial fulfilment of required of Master of


Computer Application (M.C.A.)

Department of Information Technology and


Computer Applications

Submitted To: Submitted By:


Mr. Harshit Sharma Saksham Bhargava
(Assistant Professor) 21MCAN001

BATCH – A1
DECLARATION BY THE CANDIDATE

I hereby declare that minor project report entitled “MEENA’S KITCHEN - AN E-COMMERCE
WEBSITE” submitted by me to JECRC University. In partial fulfilment of the requirement for
the award of the degree of MASTER OF COMPUTER APPLICATION is a record of bona fide
project work carried out by me under the guidance of Mr. Harshit Sharma. I further declare
that the work reported in this project has not been submitted and will not be submitted,
either in part or in full, for the award of any other degree in this institute or any other institute
or university.

Candidate Signature: Saksham Bhargava

Branch: IT Student ID: 21MCAN001

Submitted To:

Department of Information Technology,

JECRC University, Jaipur

State: Rajasthan
ABSTRACT

Meena’s Kitchen is an E-commerce website which deals with sales and purchase of
homemade snacks and beverages. The customer can place orders and make purchases of
multiple goods with the help of online payment system. The orders then get delivered to the
provided address anywhere within India.

This project is built on WordPress with is CMS – (Content Management System). A content
management system or CMS is a software that is used to build websites and create content
to be published on the internet. Typically, CMS allows you to create a website without writing
any code. It keeps the track of every piece of content on our website.
1. INTRODUCTION

This section gives a scope description and overview of everything included in this Project
Report. Also, the purpose for this document is described and system overview along with
goal and vision are listed.

1.1. Purpose

The Purpose of this document is to give a detailed description of Meena’s Kitchen


Project. It will illustrate the purpose and complete declaration for the development
of system. It will also explain system constraints, interface and interactions with
WordPress Plugins. This document is primarily intended to anyone who wants to get
an overview of how Meena’s Kitchen website works, its outcomes and possible
usages in future.

1.2. System Overview

This is an e-commerce website which deals with sales and purchase of homemade
snacks and beverages. Electronic Commerce is process of doing business through
computer networks. The customer can place orders and make purchases of multiple
goods with the help of online payment system. Unlike traditional commerce that is
carried out physically with effort of a person to go & get products, ecommerce has
made it easier for human to reduce physical work and to save time. The main
advantage of e-commerce over traditional commerce is the user can browse online
shops, compare prices and order merchandise sitting at home on their PC.

1.3. Problem Statement

The main purpose of this website is to deliver the good quality snacks at your doorstep
anywhere in India. The main advantage of e-commerce over traditional commerce is
the user can browse online shops, compare prices and order merchandise sitting at
home on their PC. The orders then get delivered to the provided address anywhere
within India.
SYSTEM DESIGN

WordPress offers a bench of free applications coded in PHP, to manage MySQL databases.
The queries are run, optimized and repaired by the user through creating, altering, dropping,
deleting, importing and exporting tables (columns, relations, indexes, users). The latter is
called PhpMyAdmin software that is integrated as well in the cPanel so that the user can
execute any SQL statement using the interface. This online database supports LTR (left to
right) and RTL (right to left) languages within 72 translations available.
E-R DIAGRAM

comment author comment date

comment date gmt


comment id comment post id

meta value comment approved


meta id
comment id

meta ke comment author

reference

wp commentmeta wp comments
meta id
post id

meta ke
meta value

wp postmeta
count
comment comment record

post name

reference
user pass
post date
wp posts

post status user email


wp users
comment count
user url

has a
user status
reference
term id

name
umeta id

slug
wp terms wp usermeta user id

term group

meta ke
has a

has a
term ta onom id meta value

descrip on

wp term ta onom
count

term id

ta onom

parent has a

ob ect id

wp term rela onships


term ta onom id

term order
DATABASE/TABLES STRUCTURE/SCHEMA

Database of Customers
List of Tables in the Database

List of Orders Database


Software Selection Criteria

Fitting with the association’s needs, the most suitable tool to be used is a Content
Management System (CMS). The latter is a system that will facilitate the management of the
virtual platform in terms of creation, modification and elimination of content, since it
leverages web-based publishing as well as controlling format and revision. It is an appropriate
tool for allowing users to change Web content, which includes the feature adjustment of legal
electronic documents formatted into HTML or PDF format for the virtual platform.
Additionally, the board members of the association can track the posted files

through the indexing of data using keywords and retrievals. Based on design and
implementation, the choice of CMS technology was due to the geographical dispersion of
Mimouna Foundation members in Morocco, so the electronic data forms can be more
accessible throughout this virtual platform. In fact, the selection of the most appropriate tool
that will be adapted in designing this virtual platform, so that it would fit the association’
requirements, was a difficult choice. The main aim was to reduce time, improve system
quality, make an operational design, and able any user (from any field of proficiency) to use
it efficiently. Therefore, a content management system was a good choice, one that satisfies
the needed software criteria for this kind of web management system. CMS and other
blogging platforms simplify publishing and sharing information in a cost-effective way via a
group of applications and tools that facilitate the organization of electronic data through
creating, editing, reviewing, and publishing, in addition, to many web-provided features and
options to index and search documents using different engines.
System Architecture
1. Hosting Server
The server, where the website of Meena’s Kitchen is hosted, is managed by Hostinger.
The Hostinger is a web-based system that let the designer control services as well as
configure open-source applications. This is an online interface that allowed the
management of the server (operating system of the hosting machine), and through it the
creation of virtual hosts can be done which contain WordPress and the PhpMyAdmin
online database.
2. Mailing System
The mailing s stem used in this virtual platform is ‘Round Cube’, which is an open-source
free solution coded mainly in PHP. It offers a friendly user interface that allows the
creation of email to be hosted in an IMAP (Internet Message Access Protocol) web server
that manages email messages and stores them. Accordingly, Mimouna Foundation
members will have their own mailing system to communicate within their personal
domain part ‘@meenakitchen.in. For instance: ‘contact@meenakitchen.in’ which will be
applied to all members. An administrator will be enabled to add and delete users’ emails
accounts based on membership status.
3. Database Management System: PhpMyAdmin
WordPress offers a bench of free applications coded in PHP, to manage MySQL databases.
The queries are run, optimized and repaired by the user through creating, altering,
dropping, deleting, importing and exporting tables (columns, relations, indexes, users).
The latter is called PhpMyAdmin software that is integrated as well in the cPanel so that
the user can execute any SQL statement using the interface. This online database supports
LTR (left to right) and RTL (right to left) languages within 72 translations available.
4. Virtual Platform

4.1. Plugins Used

4.1.1. Checkout Fields Manager for WooCommerce


4.1.2. Contact Form 7
4.1.3. FiboSearch - AJAX Search for WooCommerce Database Design
4.1.4. LiteSpeed Cache
4.1.5. Razorpay for WooCommerce
4.1.6. WooCommerce
4.1.7. WooCommerce Advanced Quantity
4.1.8. WooCommerce PDF Invoices & Packing Slips
4.1.9. WooCommerce Shipping & Tax
4.1.10. YITH WooCommerce Social Login Premium
4.1.11. Yoast SEO
4.2. Database Design

4.3. User Roles

4.3.1. Administrator
Administrators have access to the whole virtual platform by being enabled to edit
events, tasks, finance, pages, profiles, documents and features are enabled for
administrators as well as creating new users. They also have user modules to manage
access of members, especially communication members who are privileged.
4.3.2. Customer
Customer is created when they create an account on the website. This role is basically
equivalent to that of a normal blog subscriber, but customers can edit their own
account information and view past or current orders.
TECH STACK
1. Software Tools Used
The whole Project is divided in two parts the front end and the back end.
1.1. Front End
The front end is designed using of HTML, CSS, JavaScript.
HTML- HTML or Hyper Text Markup Language is the main markup language for
creating web pages and other information that can be displayed in a web
browser.HTML is written in the form of HTML elements consisting of tags enclosed in
angle brackets (like <html>), within the web page content. HTML tags most
commonly come in pairs like <h1> and </h1>, although some tags represent empty
elements and so are unpaired, for example <img>. The first tag in a pair is the start
tag, and the second tag is the end tag (they are also called opening tags and closing
tags). In between these tags web designers can add text, further tags, comments and
other types of text-based content. The purpose of a web browser is to read HTML
documents and compose them into visible or audible web pages. The browser does
not display the HTML tags, but uses the tags to interpret the content of the
page.HTML elements form the building blocks of all websites. HTML allows images
and objects to be embedded and can be used to create interactive forms. It provides
a means to create structured documents by denoting structural semantics for text
such as headings, paragraphs, lists, links, quotes and other items. It can embed
scripts written in languages such as JavaScript which affect the behaviour of HTML
web pages.
CSS - Cascading Style Sheets (CSS) is a style sheet language used for describing the
look and formatting of a document written in a markup language. While most often
used to style web pages and interfaces written in HTML and XHTML, the language
can be applied to any kind of XML document, including plain XML, SVG and XUL. CSS
is a cornerstone specification of the web and almost all web pages use CSS style
sheets to describe their presentation.CSS is designed primarily to enable the
separation of document content from document presentation, including elements
such as the layout, colours, and fonts. This separation can improve content
accessibility, provide more flexibility and control in the specification.
JavaScript - JavaScript (JS) is a dynamic computer programming language. It is most
commonly used as part of web browsers, whose implementations allow clientside
scripts to interact with the user, control the browser, communicate asynchronously,
and alter the document content that is displayed. It is also being used in server-side
programming, game development and the creation of desktop and mobile
applications. JavaScript is a prototype-based scripting language with dynamic typing
and has first-class functions. Its syntax was influenced by C. JavaScript copies many
names and naming conventions from Java, but the two languages are otherwise
unrelated and have very different semantics. The key design principles within
JavaScript are taken from the Self and Scheme programming languages. It is a multi-
paradigm language, supporting object-oriented, imperative, and functional
programming styles. The application of JavaScript to use outside of web pages—for
example, in PDF documents, site-specific browsers, and desktop widgets—is also
significant. Newer and faster JavaScript VMs and platforms built upon them (notably
Node.js) have also increased the popularity of JavaScript for server-side web
applications. On the client side, JavaScript was traditionally implemented as an
interpreted language but just-in-time compilation is now performed by recent (post-
2012) browsers.
1.2. Back End
The front end is designed using of PHP.
PHP - The PHP is a server-side scripting language designed for web development but
also used as a general-purpose programming language. PHP is now installed on more
than 244 million websites and 2.1 million web servers. Originally created by Rasmus
Lerdorf in 1995, the reference implementation of PHP is now produced by The PHP
Group. While PHP originally stood for Personal Home Page, it now stands for PHP:
Hypertext Pre-processor, a recursive backronym. PHP code is interpreted by a
webserver with a PHP processor module, which generates the resulting web page:
PHP commands can be embedded directly into an HTML source document rather
than calling an external file to process data. It has also evolved to include a
command-line interface capability and can be used in standalone graphical
applications. PHP is free software released under the PHP License. PHP can be
deployed on most web servers and also as a standalone shell on almost every
operating system and platform, free of charge.
MySQL - MySQL ("My S-Q-L", officially, but also called "My Sequel") is (as of July
2013) the world's second most widely used open-source relational database
management system (RDBMS). It is named after co-founder Michael Widenius
daughter, My. The SQL phrase stands for Structured Query Language. The MySQL
development project has made its source code available under the terms of the GNU
General Public License, as well as under a variety of proprietary agreements. MySQL
was owned and sponsored by a single for-profit firm, the Swedish company MySQL
AB, now owned by Oracle Corporation. MySQL is a popular choice of database for
use in web applications, and is a central component of the widely used LAMP open
source web application software stack (and other 'AMP' stacks). LAMP is an acronym
for "Linux, Apache, MySQL, Perl/PHP/Python." Free-software-open source projects
that require a full-featured database management system often use MySQL. For
commercial use, several paid editions are available, and offer additional
functionality. Applications which use MySQL databases
CODE
WordPress comprises different templates files as the following:
header.php: This is related to the header and navigation that can be edited within the php
file or through the Admin Panel.

<?php if (!defined('ABSPATH'))
{
exit;
} ?><!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<link rel="profile" href="https://gmpg.org/xfn/11">
<?php wp_head(); ?>
</head>

<body <?php body_class(); ?> <?php generate_do_microdata('body'); ?>>


<?php do_action('wp_body_open');
do_action('generate_before_header');
do_action('generate_header');
do_action('generate_after_header'); ?>

<div <?php generate_do_attr('page'); ?>>


<?php do_action('generate_inside_site_container'); ?>
<div <?php generate_do_attr('site-content'); ?>>
<?php do_action('generate_inside_container');

index.php: This displays the posts of the virtual platform (website) that contains a loop as
PHP codes. It is displayed in excerpt or full-length form in the front or a separate static page
of the website.

<?php if (!defined('ABSPATH'))
{
exit;
}
get_header(); ?>

<div <?php generate_do_attr('content'); ?>>


<main <?php generate_do_attr('main'); ?>>
<?php do_action('generate_before_main_content');
if (generate_has_default_loop())
{
if (have_posts()):
do_action('generate_before_loop', 'index');
while (have_posts()):
the_post();
generate_do_template_part('index');
endwhile;
do_action('generate_after_loop', 'index');
else:
generate_do_template_part('none');
endif;
}
do_action('generate_after_main_content'); ?>
</main>
</div>

<?php do_action('generate_after_primary_content_area');
generate_construct_sidebars();
get_footer();

functions.php: This is meant for users who are looking to customize their themes’ features.
This is not provided b all WordPress themes, so it needs to be created under ‘wp-
content/themes/ThemeFolder” within PHP functions.

<?php if (!defined('ABSPATH'))
{
exit;
}
define('GENERATE_VERSION', '3.1.3');
if (!function_exists('generate_setup'))
{
add_action('after_setup_theme', 'generate_setup');
function generate_setup()
{
load_theme_textdomain('generatepress');
add_theme_support('automatic-feed-links');
add_theme_support('post-thumbnails');
add_theme_support('post-formats', array(
'aside',
'image',
'video',
'quote',
'link',
'status'
));
add_theme_support('woocommerce');
add_theme_support('title-tag');
add_theme_support('html5', array(
'search-form',
'comment-form',
'comment-list',
'gallery',
'caption',
'script',
'style'
));
add_theme_support('customize-selective-refresh-widgets');
add_theme_support('align-wide');
add_theme_support('responsive-embeds');
$color_palette = generate_get_editor_color_palette();
if (!empty($color_palette))
{
add_theme_support('editor-color-palette', $color_palette);
}
add_theme_support('custom-logo', array(
'height' => 70,
'width' => 350,
'flex-height' => true,
'flex-width' => true,
));
register_nav_menus(array(
'primary' => __('Primary Menu', 'generatepress') ,
));
global $content_width;
if (!isset($content_width))
{
$content_width = 1200;
}
add_theme_support('editor-styles');
$editor_styles = apply_filters('generate_editor_styles', array(
'assets/css/admin/block-editor.css',
));
add_editor_style($editor_styles);
}
}
$theme_dir = get_template_directory();
require $theme_dir . '/inc/theme-functions.php';
require $theme_dir . '/inc/defaults.php';
require $theme_dir . '/inc/class-css.php';
require $theme_dir . '/inc/css-output.php';
require $theme_dir . '/inc/general.php';
require $theme_dir . '/inc/customizer.php';
require $theme_dir . '/inc/markup.php';
require $theme_dir . '/inc/typography.php';
require $theme_dir . '/inc/plugin-compat.php';
require $theme_dir . '/inc/block-editor.php';
require $theme_dir . '/inc/class-typography.php';
require $theme_dir . '/inc/class-typography-migration.php';
require $theme_dir . '/inc/class-html-attributes.php';
require $theme_dir . '/inc/class-theme-update.php';
require $theme_dir . '/inc/class-rest.php';
require $theme_dir . '/inc/deprecated.php';
if (is_admin())
{
require $theme_dir . '/inc/meta-box.php';
require $theme_dir . '/inc/class-dashboard.php';
}
require $theme_dir . '/inc/structure/archives.php';
require $theme_dir . '/inc/structure/comments.php';
require $theme_dir . '/inc/structure/featured-images.php';
require $theme_dir . '/inc/structure/footer.php';
require $theme_dir . '/inc/structure/header.php';
require $theme_dir . '/inc/structure/navigation.php';
require $theme_dir . '/inc/structure/post-meta.php';
require $theme_dir . '/inc/structure/sidebars.php';

footer.php: This file is used to change the footer of every web page of the website so that
JavaScript code should be added before the </body/> tag.

<?php if (!defined('ABSPATH'))
{
exit;
} ?><?php do_action('generate_before_footer'); ?><div<?php
generate_do_attr('footer'); ?>><?php
do_action('generate_before_footer_content');
do_action('generate_footer');
do_action('generate_after_footer_content'); ?></div><?php
do_action('generate_after_footer');
wp_footer(); ?>

404.php: This informs that the post or queried page is not found by WordPress

<?php if (!defined('ABSPATH'))
{
exit;
}
get_header(); ?>

<div <?php generate_do_attr('content'); ?>>


<main <?php generate_do_attr('main'); ?>>
<?php do_action('generate_before_main_content');
generate_do_template_part('404');
do_action('generate_after_main_content'); ?>
</main>
</div>

<?php do_action('generate_after_primary_content_area');
generate_construct_sidebars();
get_footer();
search.php: This is related to the search result page to be displayed.

<?php if (!defined('ABSPATH'))
{
exit;
}
get_header(); ?>

<div <?php generate_do_attr('content'); ?>>


<main <?php generate_do_attr('main'); ?>>
<?php do_action('generate_before_main_content');
if (generate_has_default_loop())
{
if (have_posts()):
do_action('generate_before_loop', 'search');
while (have_posts()):
the_post();
generate_do_template_part('search');
endwhile;
do_action('generate_after_loop', 'search');
else:
generate_do_template_part('none');
endif;
}
do_action('generate_after_main_content'); ?>
</main>
</div>

<?php do_action('generate_after_primary_content_area');
generate_construct_sidebars();
get_footer();

order-details.php: This is related to the order details from the WooCommerce plugin.

<?php defined('ABSPATH') || exit;


$order = wc_get_order($order_id);
if (!$order)
{
return;
}
$order_items = $order-
>get_items(apply_filters('woocommerce_purchase_order_item_types',
'line_item'));
$show_purchase_note = $order-
>has_status(apply_filters('woocommerce_purchase_note_order_statuses', array(
'completed',
'processing'
)));
$show_customer_details = is_user_logged_in() && $order->get_user_id() ===
get_current_user_id();
$downloads = $order->get_downloadable_items();
$show_downloads = $order->has_downloadable_item() && $order-
>is_download_permitted();
if ($show_downloads)
{
wc_get_template('order/order-downloads.php', array(
'downloads' => $downloads,
'show_title' => true,
));
} ?>
<section class="woocommerce-order-details">
<?php do_action('woocommerce_order_details_before_order_table', $order);
?>

<h2 class="woocommerce-order-details__title"><?php esc_html_e('Order


details', 'woocommerce'); ?></h2>

<table class="woocommerce-table woocommerce-table--order-details


shop_table order_details">

<thead>
<tr>
<th class="woocommerce-table__product-name product-name"><?php
esc_html_e('Product', 'woocommerce'); ?></th>
<th class="woocommerce-table__product-table product-
total"><?php esc_html_e('Total', 'woocommerce'); ?></th>
</tr>
</thead>

<tbody>
<?php
do_action('woocommerce_order_details_before_order_table_items', $order);
foreach ($order_items as $item_id => $item)
{
$product = $item->get_product();
wc_get_template('order/order-details-item.php', array(
'order' => $order,
'item_id' => $item_id,
'item' => $item,
'show_purchase_note' => $show_purchase_note,
'purchase_note' => $product ? $product->get_purchase_note() : '',
'product' => $product,
));
}
do_action('woocommerce_order_details_after_order_table_items', $order); ?>
</tbody>

<tfoot>
<?php foreach ($order->get_order_item_totals() as $key => $total)
{ ?>
<tr>
<th scope="row"><?php echo esc_html($total['label']);
?></th>
<td><?php echo ('payment_method' === $key) ?
esc_html($total['value']) : wp_kses_post($total['value']); ?></td>
</tr>
<?php
} ?>
<?php if ($order->get_customer_note()): ?>
<tr>
<th><?php esc_html_e('Note:', 'woocommerce'); ?></th>
<td><?php echo wp_kses_post(nl2br(wptexturize($order-
>get_customer_note()))); ?></td>
</tr>
<?php
endif; ?>
</tfoot>
</table>

<?php do_action('woocommerce_order_details_after_order_table', $order); ?>


</section>

<?php do_action('woocommerce_after_order_details', $order);


if ($show_customer_details)
{
wc_get_template('order/order-details-customer.php', array(
'order' => $order
));
}

tracking.php: This is the PHP code page which allows to track the order.

<?php
defined( 'ABSPATH' ) || exit;

$notes = $order->get_customer_order_notes();
?>

<p class="order-info">
<?php
echo wp_kses_post(
apply_filters(
'woocommerce_order_tracking_status',
sprintf(
__( 'Order #%1$s was placed on %2$s and is currently %3$s.',
'woocommerce' ),
'<mark class="order-number">' . $order->get_order_number() .
'</mark>',
'<mark class="order-date">' . wc_format_datetime( $order-
>get_date_created() ) . '</mark>',
'<mark class="order-status">' . wc_get_order_status_name(
$order->get_status() ) . '</mark>'
)
)
);
?>
</p>

<?php if ( $notes ) : ?>


<h2><?php esc_html_e( 'Order updates', 'woocommerce' ); ?></h2>
<ol class="commentlist notes">
<?php foreach ( $notes as $note ) : ?>
<li class="comment note">
<div class="comment_container">
<div class="comment-text">
<p class="meta"><?php echo date_i18n( esc_html__( 'l jS
\o\f F Y, h:ia', 'woocommerce' ), strtotime( $note->comment_date ) );?></p>
<div class="description">
<?php echo wpautop( wptexturize( $note-
>comment_content ) );?>
</div>
<div class="clear"></div>
</div>
<div class="clear"></div>
</div>
</li>
<?php endforeach; ?>
</ol>
<?php endif; ?>

<?php do_action( 'woocommerce_view_order', $order->get_id() ); ?>


Database Connectivity
wp-config.php - One of the most important files in your WordPress installation is the wp-
config.php file. This file is located in the root of your WordPress file directory and contains
our website’s base configuration details, such as database connection information.

<?php
define( 'WP_CACHE', true );

/** The name of the database for WordPress */


define( 'DB_NAME', 'u864599893_lLxXr' );

/** MySQL database username */


define( 'DB_USER', 'u864599893_9yTY5' );

/** MySQL database password */


define( 'DB_PASSWORD', 'xxxxxxxxx' );

/** MySQL hostname */


define( 'DB_HOST', 'mysql' );

/** Database Charset to use in creating database tables. */


define( 'DB_CHARSET', 'utf8' );

/** The Database Collate type. Don't change this if in doubt. */


define( 'DB_COLLATE', '' );

define( 'AUTH_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx' );


define( 'SECURE_AUTH_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx' );
define( 'LOGGED_IN_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx' );
define( 'NONCE_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx') ;
define( 'AUTH_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx' );
define( 'SECURE_AUTH_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx' );
define( 'LOGGED_IN_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx' );
define( 'NONCE_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx' );
define( 'WP_CACHE_KEY_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx' );

$table_prefix = 'wp_';

define( 'WP_AUTO_UPDATE_CORE', 'minor' );


/* That's all, stop editing! Happy publishing. */

/** Absolute path to the WordPress directory. */


if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', dirname( __FILE__ ) . '/' );
}

/** Sets up WordPress vars and included files. */


require_once ABSPATH . 'wp-settings.php';
Razorpay for WooCommerce

<?php
/*
* Plugin Name: Razorpay for WooCommerce
* Plugin URI: https://razorpay.com
* Description: Razorpay Payment Gateway Integration for WooCommerce
* Version: 4.1.0
* Stable tag: 4.1.0
* Author: Team Razorpay
* WC tested up to: 6.7.0
* Author URI: https://razorpay.com
*/

if ( ! defined( 'ABSPATH' ) )
{
exit; // Exit if accessed directly
}

require_once __DIR__.'/includes/razorpay-webhook.php';
require_once __DIR__.'/razorpay-sdk/Razorpay.php';
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once __DIR__.'/includes/razorpay-route.php';
require_once __DIR__ .'/includes/razorpay-route-actions.php';
require_once __DIR__.'/includes/api/api.php';
require_once __DIR__.'/includes/utils.php';
require_once __DIR__.'/includes/state-map.php';
require_once __DIR__.'/includes/plugin-instrumentation.php';
require_once __DIR__.'/includes/support/cartbounty.php';

use Razorpay\Api\Api;
use Razorpay\Api\Errors;

add_action('plugins_loaded', 'woocommerce_razorpay_init', 0);


add_action('admin_post_nopriv_rzp_wc_webhook', 'razorpay_webhook_init', 10);

// instrumentation hooks
add_action('activated_plugin', 'razorpayPluginActivated', 10, 2 );
add_action('deactivated_plugin', 'razorpayPluginDeactivated', 10, 2 );
add_action('upgrader_process_complete', 'razorpayPluginUpgraded', 10, 2);

function woocommerce_razorpay_init()
{
if (!class_exists('WC_Payment_Gateway'))
{
return;
}
class WC_Razorpay extends WC_Payment_Gateway
{
// This one stores the WooCommerce Order Id
const SESSION_KEY = 'razorpay_wc_order_id';
const RAZORPAY_PAYMENT_ID = 'razorpay_payment_id';
const RAZORPAY_ORDER_ID = 'razorpay_order_id';
const RAZORPAY_ORDER_ID_1CC = 'razorpay_order_id_1cc';
const RAZORPAY_SIGNATURE = 'razorpay_signature';
const RAZORPAY_WC_FORM_SUBMIT = 'razorpay_wc_form_submit';

const INR = 'INR';


const CAPTURE = 'capture';
const AUTHORIZE = 'authorize';
const WC_ORDER_ID = 'woocommerce_order_id';
const WC_ORDER_NUMBER = 'woocommerce_order_number';

const DEFAULT_LABEL = 'Credit Card/Debit


Card/NetBanking';
const DEFAULT_DESCRIPTION = 'Pay securely by Credit or
Debit card or Internet Banking through Razorpay.';
const DEFAULT_SUCCESS_MESSAGE = 'Thank you for shopping with
us. Your account has been charged and your transaction is successful. We will
be processing your order soon.';

protected $supportedWebhookEvents = array(


'payment.authorized',
'payment.pending',
'refund.created',
'virtual_account.credited',
'subscription.cancelled',
'subscription.paused',
'subscription.resumed'
);

protected $defaultWebhookEvents = array(


'payment.authorized' => true,
'refund.created' => true
);

protected $visibleSettings = array(


'enabled',
'title',
'description',
'key_id',
'key_secret',
'payment_action',
'order_success_message',
'route_enable',
'enable_1cc_debug_mode',
);

public $form_fields = array();

public $supports = array(


'products',
'refunds'
);

/**
* Can be set to true if you want payment fields
* to show on the checkout (if doing a direct integration).
* @var boolean
*/
public $has_fields = false;

/**
* Unique ID for the gateway
* @var string
*/
public $id = 'razorpay';

/**
* Title of the payment method shown on the admin page.
* @var string
*/
public $method_title = 'Razorpay';

/**
* Description of the payment method shown on the admin page.
* @var string
*/
public $method_description = 'Allow customers to securely pay via
Razorpay (Credit/Debit Cards, NetBanking, UPI, Wallets)';

/**
* Icon URL, set in constructor
* @var string
*/
public $icon;

/**
* TODO: Remove usage of $this->msg
*/
protected $msg = array(
'message' => '',
'class' => '',
);

/**
* Return Wordpress plugin settings
* @param string $key setting key
* @return mixed setting value
*/
public function getSetting($key)
{
return $this->get_option($key);
}

public function getCustomOrdercreationMessage($thank_you_title,


$order)
{
$message = $this->getSetting('order_success_message');
if (isset($message) === false)
{
$message = static::DEFAULT_SUCCESS_MESSAGE;
}
return $message;
}

/**
* @param boolean $hooks Whether or not to
* setup the hooks on
* calling the constructor
*/
public function __construct($hooks = true)
{
$this->icon
= "https://cdn.razorpay.com/static/assets/logo/payment.svg";
// 1cc flags should be enabled only if merchant has access to 1cc
feature
$is1ccAvailable = false;

// Load preference API call only for administrative interface


page.
if (is_admin())
{
if (!empty($this->getSetting('key_id')) && !empty($this-
>getSetting('key_secret')))
{
try {

$api = $this->getRazorpayApiInstance();
$merchantPreferences = $api->request->request('GET',
'merchant/1cc_preferences');

if
(!empty($merchantPreferences['features']['one_click_checkout'])) {
$is1ccAvailable = true;
}

} catch (\Exception $e) {


rzpLogError($e->getMessage());
}

}
}

if ($is1ccAvailable) {
$this->visibleSettings = array_merge($this->visibleSettings,
array(
'enable_1cc',
'enable_1cc_mandatory_login',
'enable_1cc_test_mode',
'enable_1cc_pdp_checkout',
'enable_1cc_mini_cart_checkout',
'enable_1cc_ga_analytics',
'enable_1cc_fb_analytics',
'1cc_min_cart_amount',
'1cc_min_COD_slab_amount',
'1cc_max_COD_slab_amount',
));
}

$this->init_form_fields();
$this->init_settings();

// TODO: This is hacky, find a better way to do this


// See mergeSettingsWithParentPlugin() in subscriptions for more
details.
if ($hooks)
{
$this->initHooks();
}

$this->title = $this->getSetting('title');
}

protected function initHooks()


{
add_action('init', array(&$this, 'check_razorpay_response'));
add_action('woocommerce_receipt_' . $this->id, array($this,
'receipt_page'));

add_action('woocommerce_api_' . $this->id, array($this,


'check_razorpay_response'));

$cb = array($this, 'process_admin_options');

if (version_compare(WOOCOMMERCE_VERSION, '2.0.0', '>='))


{
add_action(
"woocommerce_update_options_payment_gateways_{$this->id}", array($this,
'pluginInstrumentation'));
add_action("woocommerce_update_options_payment_gateways_{$this
->id}", $cb);
add_action(
"woocommerce_update_options_payment_gateways_{$this->id}", array($this,
'autoEnableWebhook'));
add_action(
"woocommerce_update_options_payment_gateways_{$this->id}", array($this,
'addAdminCheckoutSettingsAlert'));
}
else
{
add_action( "woocommerce_update_options_payment_gateways",
array($this, 'pluginInstrumentation'));
add_action('woocommerce_update_options_payment_gateways',
$cb);
add_action( "woocommerce_update_options_payment_gateways",
array($this, 'autoEnableWebhook'));
add_action( "woocommerce_update_options_payment_gateways",
array($this, 'addAdminCheckoutSettingsAlert'));
}

add_filter( 'woocommerce_thankyou_order_received_text',
array($this, 'getCustomOrdercreationMessage'), 20, 2 );
}

public function init_form_fields()


{
$webhookUrl = esc_url(admin_url('admin-post.php')) .
'?action=rzp_wc_webhook';

$defaultFormFields = array(
'enabled' => array(
'title' => __('Enable/Disable', $this->id),
'type' => 'checkbox',
'label' => __('Enable this module?', $this->id),
'default' => 'yes'
),
'title' => array(
'title' => __('Title', $this->id),
'type'=> 'text',
'description' => __('This controls the title which the
user sees during checkout.', $this->id),
'default' => __(static::DEFAULT_LABEL, $this->id)
),
'description' => array(
'title' => __('Description', $this->id),
'type' => 'textarea',
'description' => __('This controls the description which
the user sees during checkout.', $this->id),
'default' => __(static::DEFAULT_DESCRIPTION, $this->id)
),
'key_id' => array(
'title' => __('Key ID', $this->id),
'type' => 'text',
'description' => __('The key Id and key secret can be
generated from "API Keys" section of Razorpay Dashboard. Use test or live for
test or live mode.', $this->id)
),
'key_secret' => array(
'title' => __('Key Secret', $this->id),
'type' => 'text',
'description' => __('The key Id and key secret can be
generated from "API Keys" section of Razorpay Dashboard. Use test or live for
test or live mode.', $this->id)
),
'payment_action' => array(
'title' => __('Payment Action', $this->id),
'type' => 'select',
'description' => __('Payment action on order compelete',
$this->id),
'default' => self::CAPTURE,
'options' => array(
self::AUTHORIZE => 'Authorize',
self::CAPTURE => 'Authorize and Capture'
)
),
'order_success_message' => array(
'title' => __('Order Completion Message', $this->id),
'type' => 'textarea',
'description' => __('Message to be displayed after a
successful order', $this->id),
'default' => __(STATIC::DEFAULT_SUCCESS_MESSAGE, $this-
>id),
),
'enable_1cc_debug_mode' => array( //Added this config for both
native and 1cc merchants
'title' => __('Activate debug mode'),
'type' => 'checkbox',
'description' => 'When debug mode is active, API logs and
errors are collected and stored in your Woocommerce dashboard. It is
recommended to keep this activated.',
'label' => __('Enable debug mode'),
'default' => 'yes',
),
);

do_action_ref_array( 'setup_extra_setting_fields', array(


&$defaultFormFields ) );

foreach ($defaultFormFields as $key => $value)


{
if (in_array($key, $this->visibleSettings, true))
{
$this->form_fields[$key] = $value;
}
}
}

public function autoEnableWebhook()


{
$webhookExist = false;
$webhookUrl = esc_url(admin_url('admin-post.php')) .
'?action=rzp_wc_webhook';

$key_id = $this->getSetting('key_id');
$key_secret = $this->getSetting('key_secret');
$enabled = true;
$secret = empty($this->getSetting('webhook_secret')) ? $this-
>generateSecret() : $this->getSetting('webhook_secret');

$this->update_option('webhook_secret', $secret);
$getWebhookFlag = get_option('webhook_enable_flag');
$time = time();

if (empty($getWebhookFlag))
{
add_option('webhook_enable_flag', $time);
}
else
{
update_option('webhook_enable_flag', $time);
}
//validating the key id and key secret set properly or not.
if($key_id == null || $key_secret == null)
{
?>
<div class="notice error is-dismissible" >
<p><b><?php _e( 'Key Id and Key Secret are required.');
?><b></p>
</div>
<?php

rzpLogError('Key Id and Key Secret are required.');


return;
}

$domain = parse_url($webhookUrl, PHP_URL_HOST);

$domain_ip = gethostbyname($domain);

if (!filter_var($domain_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 |


FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))
{

?>
<div class="notice error is-dismissible" >
<p><b><?php _e( 'Could not enable webhook for localhost
server.'); ?><b></p>
</div>
<?php

rzpLogError('Could not enable webhook for localhost');


return;
}
$skip = 0;
$count = 10;
$webhookItems= [];

do {
$webhook = $this->webhookAPI("GET",
"webhooks?count=".$count."&skip=".$skip);
$skip += 10;
if ($webhook['count'] > 0)
{
foreach ($webhook['items'] as $key => $value)
{
$webhookItems[] = $value;
}
}
} while ( $webhook['count'] === $count);

$data = [
'url' => $webhookUrl,
'active' => $enabled,
'events' => $this->defaultWebhookEvents,
'secret' => $secret,
];

if (count($webhookItems) > 0)
{
foreach ($webhookItems as $key => $value)
{
if ($value['url'] === $webhookUrl)
{
foreach ($value['events'] as $evntkey => $evntval)
{
if (($evntval == 1) and
(in_array($evntkey, $this-
>supportedWebhookEvents) === true))
{
$this->defaultWebhookEvents[$evntkey] = true;
}
}

$data = [
'url' => $webhookUrl,
'active' => $enabled,
'events' => $this->defaultWebhookEvents,
'secret' => $secret,
];
$webhookExist = true;
$webhookId = $value['id'];
}
}
}
if ($webhookExist)
{
rzpLogInfo('Updating razorpay webhook');
$this->webhookAPI('PUT', "webhooks/".$webhookId, $data);
}
else
{
rzpLogInfo('Creating razorpay webhook');
$this->webhookAPI('POST', "webhooks/", $data);
}
}

public function pluginInstrumentation()


{
if (empty($_POST['woocommerce_razorpay_key_id']) or
empty($_POST['woocommerce_razorpay_key_secret']))
{
error_log('Key Id and Key Secret are required.');
return;
}

$trackObject = new
TrackPluginInstrumentation($_POST['woocommerce_razorpay_key_id'],
$_POST['woocommerce_razorpay_key_secret']);

$existingVersion = get_option('rzp_woocommerce_current_version');

if(isset($existingVersion))
{
update_option('rzp_woocommerce_current_version',
get_plugin_data(__FILE__)['Version']);
}
else
{
add_option('rzp_woocommerce_current_version',
get_plugin_data(__FILE__)['Version']);
}

try
{
global $wpdb;
$isTransactingUser = false;

$rzpTrancationData = $wpdb->get_row($wpdb->prepare("SELECT
post_id FROM $wpdb->postmeta AS P WHERE meta_key = %s AND meta_value = %s",
"_payment_method", "razorpay"));

$arrayPost = json_decode(json_encode($rzpTrancationData),
true);

if (empty($arrayPost) === false and


($arrayPost == null) === false)
{
$isTransactingUser = true;
}
}
catch (\Razorpay\Api\Errors\Error $e)
{
?>
<div class="notice error is-dismissible" >
<p><b><?php _e( 'Please check Key Id and Key Secret.');
?></b></p>
</div>
<?php
error_log('Please check Key Id and Key Secret.');
return;
}
catch (\Exception $e)
{
?>
<div class="notice error is-dismissible" >
<p><b><?php _e( 'something went wrong'); ?></b></p>
</div>
<?php
error_log($e->getMessage());
return;
}

$authEvent = '';
$pluginStatusEvent = '';

$authProperties = [
'is_key_id_populated' => true,
'is_key_secret_populated' => true,
'page_url' => $_SERVER['REQUEST_SCHEME'] .
'://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'],
'auth_successful_status' => true,
'is_plugin_activated' =>
(isset($_POST['woocommerce_razorpay_enabled'])) ? true :false
];

// for enable and disable plugin


$pluginStatusProperties = [
'current_status' => ($this-
>getSetting('enabled')==='yes') ? 'enabled' :'disabled',
'is_transacting_user' => $isTransactingUser
];

if (empty($this->getSetting('key_id')) and
empty($this->getSetting('key_secret')))
{
$authEvent = 'saving auth details';

if(empty($_POST['woocommerce_razorpay_enabled']) === false)


{
$pluginStatusEvent = 'plugin enabled';
}
}
else
{
$authEvent = 'updating auth details';
}

$response = $trackObject->rzpTrackSegment($authEvent,
$authProperties);

if ((empty($_POST['woocommerce_razorpay_enabled']) === false) and


($this->getSetting('enabled') === 'no'))
{
$pluginStatusEvent = 'plugin enabled';
}
elseif ((empty($_POST['woocommerce_razorpay_enabled']) === true)
and
($this->getSetting('enabled') === 'yes'))
{
$pluginStatusEvent = 'plugin disabled';
}

if ($pluginStatusEvent !== '')


{
$response = $trackObject->rzpTrackSegment($pluginStatusEvent,
$pluginStatusProperties);
}
}

public function generateSecret()


{
$alphanumericString = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-
=~!@#$%^&*()_+,./<>?;:[]{}|abcdefghijklmnopqrstuvwxyz';
$secret = substr(str_shuffle($alphanumericString), 0, 20);

return $secret;
}

// showing notice : status of 1cc active / inactive message in admin


dashboard
function addAdminCheckoutSettingsAlert() {
$enable_1cc = $this->getSetting('enable_1cc');
if($enable_1cc == 'no')
{
?>
<div class="notice error is-dismissible" >
<p><b><?php _e( 'We are sorry to see you opt out of
Magic Checkout experience. Please help us understand what went wrong by
filling up this form.'); ?></b></p>
</div>
<?php
error_log('1cc is disabled.');
return;
}
elseif ($enable_1cc == 'yes')
{
?>
<div class="notice notice-success is-dismissible" >
<p><b><?php _e( 'You are Live with Magic Checkout.');
?></b></p>
</div>
<?php
return;
}
}

protected function webhookAPI($method, $url, $data = array())


{
$webhook = [];
try
{
$api = $this->getRazorpayApiInstance();

$webhook = $api->request->request($method, $url, $data);


}
catch(Exception $e)
{
$log = array(
'message' => $e->getMessage(),
);

error_log(json_encode($log));
rzpLogError(json_encode($log));
}

return $webhook;
}

public function admin_options()


{
echo '<h3>'.__('Razorpay Payment Gateway', $this->id) . '</h3>';
echo '<p>'.__('Allows payments by Credit/Debit Cards, NetBanking,
UPI, and multiple Wallets') . '</p>';
echo '<table class="form-table">';
// Generate the HTML For the settings form.
$this->generate_settings_html();
echo '</table>';
}

public function get_description()


{
return $this->getSetting('description');
}

/**
* Receipt Page
* @param string $orderId WC Order Id
**/
function receipt_page($orderId)
{
echo $this->generate_razorpay_form($orderId);
}

/**
* Returns key to use in session for storing Razorpay order Id
* @param string $orderId Razorpay Order Id
* @return string Session Key
*/
protected function getOrderSessionKey($orderId)
{
$is1ccOrder = get_post_meta( $orderId, 'is_magic_checkout_order',
true );

if($is1ccOrder == 'yes')
{
return self::RAZORPAY_ORDER_ID_1CC . $orderId;
}
return self::RAZORPAY_ORDER_ID . $orderId;
}

/**
* Given a order Id, find the associated
* Razorpay Order from the session and verify
* that is is still correct. If not found
* (or incorrect), create a new Razorpay Order
*
* @param string $orderId Order Id
* @return mixed Razorpay Order Id or Exception
*/
public function createOrGetRazorpayOrderId($orderId, $is1ccCheckout =
'no')
{
global $woocommerce;
rzpLogInfo("createOrGetRazorpayOrderId $orderId and is1ccCheckout
is set to $is1ccCheckout");

$create = false;

if($is1ccCheckout == 'no')
{
update_post_meta( $orderId, 'is_magic_checkout_order', 'no' );

rzpLogInfo("Called createOrGetRazorpayOrderId with params


orderId $orderId and is_magic_checkout_order is set to no");
}

$sessionKey = $this->getOrderSessionKey($orderId);

try
{
$razorpayOrderId = get_transient($sessionKey);
rzpLogInfo("razorpayOrderId $razorpayOrderId | sessionKey
$sessionKey");
// If we don't have an Order
// or the if the order is present in transient but doesn't
match what we have saved
if (($razorpayOrderId === false) or
(($razorpayOrderId and ($this-
>verifyOrderAmount($razorpayOrderId, $orderId, $is1ccCheckout)) === false)))
{
$create = true;
}
else
{
return $razorpayOrderId;
}
}
// Order doesn't exist or verification failed
// So try creating one
catch (Exception $e)
{
$create = true;
}

if ($create)
{
try
{
return $this->createRazorpayOrderId($orderId,
$sessionKey);
}
// For the bad request errors, it's safe to show the
message to the customer.
catch (Errors\BadRequestError $e)
{
return $e;
}
// For any other exceptions, we make sure that the error
message
// does not propagate to the front-end.
catch (Exception $e)
{
return new Exception("Payment failed");
}
}
}

/**
* Returns redirect URL post payment processing
* @return string redirect URL
*/
private function getRedirectUrl($orderId)
{
$order = wc_get_order($orderId);

$query = [
'wc-api' => $this->id,
'order_key' => $order->get_order_key(),
];

return add_query_arg($query, trailingslashit(get_home_url()));


}

/**
* Specific payment parameters to be passed to checkout
* for payment processing
* @param string $orderId WC Order Id
* @return array payment params
*/
protected function getRazorpayPaymentParams($orderId)
{
$getWebhookFlag = get_option('webhook_enable_flag');
$time = time();
if (empty($getWebhookFlag) == false)
{
if ($getWebhookFlag + 86400 < time())
{
$this->autoEnableWebhook();
}
}
else
{
update_option('webhook_enable_flag', $time);
$this->autoEnableWebhook();
}

rzpLogInfo("getRazorpayPaymentParams $orderId");
$razorpayOrderId = $this->createOrGetRazorpayOrderId($orderId);

if ($razorpayOrderId === null)


{
throw new Exception('RAZORPAY ERROR: Razorpay API could not be
reached');
}
else if ($razorpayOrderId instanceof Exception)
{
$message = $razorpayOrderId->getMessage();

throw new Exception("RAZORPAY ERROR: Order creation failed


with the message: '$message'.");
}

return [
'order_id' => $razorpayOrderId
];
}

/**
* Generate razorpay button link
* @param string $orderId WC Order Id
**/
public function generate_razorpay_form($orderId)
{
$order = wc_get_order($orderId);

try
{
$params = $this->getRazorpayPaymentParams($orderId);
}
catch (Exception $e)
{
return $e->getMessage();
}
$checkoutArgs = $this->getCheckoutArguments($order, $params);

$html = '<p>'.__('Thank you for your order, please click the


button below to pay with Razorpay.', $this->id).'</p>';

$html .= $this->generateOrderForm($checkoutArgs);

return $html;
}

/**
* default parameters passed to checkout
* @param WC_Order $order WC Order
* @return array checkout params
*/
public function getDefaultCheckoutArguments($order)
{
global $woocommerce;

$orderId = $order->get_order_number();

$wcOrderId = $order->get_id();

$callbackUrl = $this->getRedirectUrl($wcOrderId);

$sessionKey = $this->getOrderSessionKey($wcOrderId);

$razorpayOrderId = get_transient($sessionKey);

$productinfo = "Order $orderId";

return array(
'key' => $this->getSetting('key_id'),
'name' => html_entity_decode(get_bloginfo('name'),
ENT_QUOTES),
'currency' => self::INR,
'description' => $productinfo,
'notes' => array(
self::WC_ORDER_ID => $orderId,
self::WC_ORDER_NUMBER => $wcOrderId
),
'order_id' => $razorpayOrderId,
'callback_url' => $callbackUrl,
'prefill' => $this->getCustomerInfo($order)
);
}

/**
* @param WC_Order $order
* @return string currency
*/
private function getOrderCurrency($order)
{
if (version_compare(WOOCOMMERCE_VERSION, '2.7.0', '>='))
{
return $order->get_currency();
}

return $order->get_order_currency();
}

/**
* Returns array of checkout params
*/
private function getCheckoutArguments($order, $params)
{
$args = $this->getDefaultCheckoutArguments($order);

$currency = $this->getOrderCurrency($order);

// The list of valid currencies is at


https://razorpay.freshdesk.com/support/solutions/articles/11000065530-what-
currencies-does-razorpay-support-

$args = array_merge($args, $params);

return $args;
}

public function getCustomerInfo($order)


{
if (version_compare(WOOCOMMERCE_VERSION, '2.7.0', '>='))
{
$args = array(
'name' => $order->get_billing_first_name() . ' ' .
$order->get_billing_last_name(),
'email' => $order->get_billing_email(),
'contact' => $order->get_billing_phone(),
);
}
else
{
$args = array(
'name' => $order->billing_first_name . ' ' . $order-
>billing_last_name,
'email' => $order->billing_email,
'contact' => $order->billing_phone,
);
}

return $args;
}

protected function createRazorpayOrderId($orderId, $sessionKey)


{
rzpLogInfo("Called createRazorpayOrderId with params orderId
$orderId and sessionKey $sessionKey");

// Calls the helper function to create order data


global $woocommerce;

$api = $this->getRazorpayApiInstance();

$data = $this->getOrderCreationData($orderId);
rzpLogInfo('For order ' . $orderId);
rzpLogInfo(json_encode($data));
try
{
$razorpayOrder = $api->order->create($data);
}
catch (Exception $e)
{
return $e;
}

$getWebhookFlag = get_option('webhook_enable_flag');
$time = time();

if (empty($getWebhookFlag) == false)
{
if ($getWebhookFlag + 43200 < time())
{
$this->autoEnableWebhook();
}
}
else
{
update_option('webhook_enable_flag', $time);
$this->autoEnableWebhook();
}

$razorpayOrderId = $razorpayOrder['id'];

// Storing the razorpay order id in transient for 5 hours time.


set_transient($sessionKey, $razorpayOrderId, 18000);

// By default woocommerce session TTL is 48 hours.


$woocommerce->session->set($sessionKey, $razorpayOrderId);

rzpLogInfo('For order session key ' . $sessionKey);


//update it in order comments
$order = wc_get_order($orderId);

$order->add_order_note("Razorpay OrderId: $razorpayOrderId");

return $razorpayOrderId;
}

protected function verifyOrderAmount($razorpayOrderId, $orderId,


$is1ccCheckout = 'no')
{
rzpLogInfo("Called verifyOrderAmount with params orderId $orderId
and rzporderId $razorpayOrderId");
$order = wc_get_order($orderId);

$api = $this->getRazorpayApiInstance();

try
{
$razorpayOrder = $api->order->fetch($razorpayOrderId);
}
catch (Exception $e)
{
$message = $e->getMessage();
rzpLogInfo("Failed at verifyOrderAmount with $message");
return "RAZORPAY ERROR: Order fetch failed with the message
'$message'";
}

$orderCreationData = $this->getOrderCreationData($orderId);

$razorpayOrderArgs = array(
'id' => $razorpayOrderId,
'currency' => $orderCreationData['currency'],
'receipt' => (string) $orderId,
);

if($is1ccCheckout == 'no'){
$razorpayOrderArgs['amount'] = $orderCreationData['amount'];
}else{
$razorpayOrderArgs['line_items_total'] =
$orderCreationData['amount'];
}

$orderKeys = array_keys($razorpayOrderArgs);

foreach ($orderKeys as $key)


{
if ($razorpayOrderArgs[$key] !== $razorpayOrder[$key])
{
return false;
}
}

return true;
}

private function getOrderCreationData($orderId)


{
rzpLogInfo("Called getOrderCreationData with params orderId
$orderId");
$order = wc_get_order($orderId);

$is1ccOrder = get_post_meta( $orderId, 'is_magic_checkout_order',


true );

$data = array(
'receipt' => $orderId,
'amount' => (int) round($order->get_total() * 100),
'currency' => $this->getOrderCurrency($order),
'payment_capture' => ($this->getSetting('payment_action') ===
self::AUTHORIZE) ? 0 : 1,
'app_offer' => ($order->get_discount_total() > 0) ? 1 :
0,
'notes' => array(
self::WC_ORDER_NUMBER => (string) $orderId,
),
);

if ($this->getSetting('route_enable') == 'yes')
{
$razorpayRoute = new RZP_Route_Action();
$orderTransferArr = $razorpayRoute-
>getOrderTransferData($orderId);

if(isset($orderTransferArr) && !empty($orderTransferArr)){


$transferData = array(
'transfers' => $orderTransferArr
);
$data = array_merge($data,$transferData);
}
}

rzpLogInfo("Called getOrderCreationData with params orderId


$orderId and is1ccOrder is set to $is1ccOrder");

if (is1ccEnabled() && !empty($is1ccOrder) && $is1ccOrder == 'yes')


{
$data = $this->orderArg1CC($data, $order);
rzpLogInfo("Called getOrderCreationData with params orderId
$orderId and adding line_items_total");
}

return $data;
}

public function orderArg1CC($data, $order)


{
// TODO: trim to 2 deciamls
$data['line_items_total'] = $order->get_total()*100;

$i = 0;
// Get and Loop Over Order Items
foreach ( $order->get_items() as $item_id => $item )
{
$product = $item->get_product();
$productDetails = $product->get_data();

$data['line_items'][$i]['type'] = "e-commerce";
$data['line_items'][$i]['sku'] = $product->get_sku();
$data['line_items'][$i]['variant_id'] = $item-
>get_variation_id();
$data['line_items'][$i]['price'] =
(empty($productDetails['price'])=== false) ?
round(wc_get_price_excluding_tax($product)*100) + round($item-
>get_subtotal_tax()*100 / $item->get_quantity()) : 0;
$data['line_items'][$i]['offer_price'] =
(empty($productDetails['sale_price'])=== false) ? (int)
$productDetails['sale_price']*100 : $productDetails['price']*100;
$data['line_items'][$i]['quantity'] = (int)$item-
>get_quantity();
$data['line_items'][$i]['name'] = mb_substr($item->get_name(),
0, 125, "UTF-8");
$data['line_items'][$i]['description'] = mb_substr($item-
>get_name(), 0, 250,"UTF-8");
$productImage = $product->get_image_id()?? null;
$data['line_items'][$i]['image_url'] = $productImage?
wp_get_attachment_url( $productImage ) : null;
$data['line_items'][$i]['product_url'] = $product-
>get_permalink();

$i++;
}

return $data;
}

private function enqueueCheckoutScripts($data)


{
if($data === 'checkoutForm' || $data === 'routeAnalyticsForm')
{
wp_register_script('razorpay_wc_script',
plugin_dir_url(__FILE__) . 'script.js',
null, null);
}
else
{
wp_register_script('razorpay_wc_script',
plugin_dir_url(__FILE__) . 'script.js',
array('razorpay_checkout'));

wp_register_script('razorpay_checkout',
'https://checkout.razorpay.com/v1/checkout.js',
null, null);
}

wp_localize_script('razorpay_wc_script',
'razorpay_wc_checkout_vars',
$data
);

wp_enqueue_script('razorpay_wc_script');
}

private function hostCheckoutScripts($data)


{
$url = Api::getFullUrl("checkout/embedded");

$formFields = "";
foreach ($data as $fieldKey => $val) {
if(in_array($fieldKey, array('notes', 'prefill', '_')))
{
foreach ($data[$fieldKey] as $field => $fieldVal) {
$formFields .= "<input type='hidden' name='$fieldKey"
."[$field]"."' value='$fieldVal'> \n";
}
}
}

return '<form method="POST" action="'.$url.'" id="checkoutForm">


<input type="hidden" name="key_id"
value="'.$data['key'].'">
<input type="hidden" name="order_id"
value="'.$data['order_id'].'">
<input type="hidden" name="name"
value="'.$data['name'].'">
<input type="hidden" name="description"
value="'.$data['description'].'">
<input type="hidden" name="image"
value="'.$data['preference']['image'].'">
<input type="hidden" name="callback_url"
value="'.$data['callback_url'].'">
<input type="hidden" name="cancel_url"
value="'.$data['cancel_url'].'">
'. $formFields .'
</form>';

/**
* Generates the order form
**/
function generateOrderForm($data)
{
$data["_"] = $this->getVersionMetaInfo($data);

$wooOrderId = $data['notes']['woocommerce_order_number'];

$redirectUrl = $this->getRedirectUrl($wooOrderId);

$data['cancel_url'] = wc_get_checkout_url();

$api = new Api($this->getSetting('key_id'),"");

$merchantPreferences = $api->request->request("GET",
"preferences");

if(isset($merchantPreferences['options']['redirect']) &&
$merchantPreferences['options']['redirect'] === true)
{
$this->enqueueCheckoutScripts('checkoutForm');

$data['preference']['image'] =
$merchantPreferences['options']['image'];
return $this->hostCheckoutScripts($data);

} else {
$this->enqueueCheckoutScripts($data);

return <<<EOT
<form name='razorpayform' action="$redirectUrl" method="POST">
<input type="hidden" name="razorpay_payment_id" id="razorpay_payment_id">
<input type="hidden" name="razorpay_signature" id="razorpay_signature" >
<!-- This distinguishes all our various wordpress plugins -->
<input type="hidden" name="razorpay_wc_form_submit" value="1">
</form>
<p id="msg-razorpay-success" class="woocommerce-info woocommerce-message"
style="display:none">
Please wait while we are processing your payment.
</p>
<p>
<button id="btn-razorpay">Pay Now</button>
<button id="btn-razorpay-cancel"
onclick="document.razorpayform.submit()">Cancel</button>
</p>
EOT;
}
}

/**
* Gets the Order Key from the Order
* for all WC versions that we suport
*/
protected function getOrderKey($order)
{
$orderKey = null;

if (version_compare(WOOCOMMERCE_VERSION, '3.0.0', '>='))


{
return $order->get_order_key();
}

return $order->order_key;
}

public function process_refund($orderId, $amount = null, $reason = '')


{
$order = wc_get_order($orderId);

if (! $order or ! $order->get_transaction_id())
{
return new WP_Error('error', __('Refund failed: No transaction
ID', 'woocommerce'));
}

$client = $this->getRazorpayApiInstance();

$paymentId = $order->get_transaction_id();

$data = array(
'amount' => (int) round($amount * 100),
'notes' => array(
'reason' => $reason,
'order_id' => $orderId,
'refund_from_website' => true,
'source' => 'woocommerce',
)
);

try
{
$refund = $client->payment
->fetch( $paymentId )
->refund( $data );

$order->add_order_note( __( 'Refund Id: ' . $refund->id,


'woocommerce' ) );
/**
* @var $refund ->id -- Provides the RazorPay Refund ID
* @var $orderId -> Refunded Order ID
* @var $refund -> WooCommerce Refund Instance.
*/
do_action( 'woo_razorpay_refund_success', $refund->id,
$orderId, $refund );

return true;
}
catch(Exception $e)
{
return new WP_Error('error', __($e->getMessage(),
'woocommerce'));
}
}

/**
* Process the payment and return the result
**/
function process_payment($order_id)
{
rzpLogInfo("Called process_payment with params order_id
$order_id");

global $woocommerce;

$order = wc_get_order($order_id);

set_transient(self::SESSION_KEY, $order_id, 3600);


rzpLogInfo("Set transient with key " . self::SESSION_KEY . "
params order_id $order_id");

$orderKey = $this->getOrderKey($order);

if (version_compare(WOOCOMMERCE_VERSION, '2.1', '>='))


{
return array(
'result' => 'success',
'redirect' => add_query_arg('key', $orderKey, $order-
>get_checkout_payment_url(true))
);
}
else if (version_compare(WOOCOMMERCE_VERSION, '2.0.0', '>='))
{
return array(
'result' => 'success',
'redirect' => add_query_arg('order', $order->get_id(),
add_query_arg('key', $orderKey, $order-
>get_checkout_payment_url(true)))
);
}
else
{
return array(
'result' => 'success',
'redirect' => add_query_arg('order', $order->get_id(),
add_query_arg('key', $orderKey,
get_permalink(get_option('woocommerce_pay_page_id'))))
);
}
}

public function getRazorpayApiInstance()


{
return new Api($this->getSetting('key_id'), $this-
>getSetting('key_secret'));
}

/**
* Check for valid razorpay server callback
* Called once payment is completed using redirect method
*/
function check_razorpay_response()
{
global $woocommerce;
global $wpdb;

$order = false;

$post_type = 'shop_order';

$post_password = sanitize_text_field($_GET['order_key']);

rzpLogInfo("Called check_razorpay_response: $post_password");

$meta_key = '_order_key';

$postMetaData = $wpdb->get_row( $wpdb->prepare("SELECT post_id


FROM $wpdb->postmeta AS P WHERE meta_key = %s AND meta_value = %s", $meta_key,
$post_password ) );

$postData = $wpdb->get_row( $wpdb->prepare("SELECT post_status


FROM $wpdb->posts AS P WHERE post_type=%s and ID=%s", $post_type,
$postMetaData->post_id) );

$arrayPost = json_decode(json_encode($postMetaData), true);

if (!empty($arrayPost) and
$arrayPost != null)
{
$orderId = $postMetaData->post_id;

if ($postData->post_status === 'draft')


{
updateOrderStatus($orderId, 'wc-pending');
}

$order = wc_get_order($orderId);
rzpLogInfo("Get order id in check_razorpay_response: orderId
$orderId");
}

// TODO: Handle redirect


if ($order === false)
{
// TODO: Add test mode condition
if (is1ccEnabled())
{
rzpLogInfo("Order details not found for the orderId:
$orderId");

wp_redirect(wc_get_cart_url());
exit;
}
wp_redirect(wc_get_checkout_url());
exit;
}

// If the order has already been paid for


// redirect user to success page
if ($order->needs_payment() === false)
{
rzpLogInfo("Order payment is already done for the orderId:
$orderId");

$cartHash = get_transient(RZP_1CC_CART_HASH.$orderId);

if ($cartHash != false)
{
// Need to delete the cart hash stored in transient.
// Becuase the cart hash will be depending on the cart items
so this will cause the issue when order api triggers.
$woocommerce->session->__unset(RZP_1CC_CART_HASH.$cartHash);
}

$this->redirectUser($order);
}

$razorpayPaymentId = null;

if ($orderId and !empty($_POST[self::RAZORPAY_PAYMENT_ID]))


{
$error = "";
$success = false;

try
{
$this->verifySignature($orderId);
$success = true;
$razorpayPaymentId =
sanitize_text_field($_POST[self::RAZORPAY_PAYMENT_ID]);

$cartHash = get_transient(RZP_1CC_CART_HASH.$orderId);

if ($cartHash != false)
{
// Need to delete the cart hash stored in transient.
// Becuase the cart hash will be depending on the cart
items so this will cause the issue when order api triggers.
$woocommerce->session-
>__unset(RZP_1CC_CART_HASH.$cartHash);
}
}
catch (Errors\SignatureVerificationError $e)
{
$error = 'WOOCOMMERCE_ERROR: Payment to Razorpay Failed. '
. $e->getMessage();
}
}
else
{
if(isset($_POST[self::RAZORPAY_WC_FORM_SUBMIT]) &&
$_POST[self::RAZORPAY_WC_FORM_SUBMIT] ==1)
{
$success = false;
$error = 'Customer cancelled the payment';
}
else
{
$success = false;
$error = "Payment Failed.";
}

$is1ccOrder = get_post_meta( $orderId,


'is_magic_checkout_order', true );

if (is1ccEnabled() && !empty($is1ccOrder) && $is1ccOrder ==


'yes')
{
$api = $this->getRazorpayApiInstance();
$sessionKey = $this->getOrderSessionKey($orderId);

//Check the transient data for razorpay order id, if it's


not available then look into session data.
if(get_transient($sessionKey))
{
$razorpayOrderId = get_transient($sessionKey);
}
else
{
$razorpayOrderId = $woocommerce->session-
>get($sessionKey);
}
$razorpayData = $api->order->fetch($razorpayOrderId);

$this->updateOrderAddress($razorpayData, $order);
}

$this->handleErrorCase($order);
$this->updateOrder($order, $success, $error,
$razorpayPaymentId, null);

if (is1ccEnabled())
{
wp_redirect(wc_get_cart_url());
exit;
}

wp_redirect(wc_get_checkout_url());
exit;
}

$this->updateOrder($order, $success, $error, $razorpayPaymentId,


null);

$this->redirectUser($order);
}

protected function redirectUser($order)


{
$redirectUrl = $this->get_return_url($order);

wp_redirect($redirectUrl);
exit;
}

protected function verifySignature($orderId)


{
rzpLogInfo("verifySignature orderId: $orderId");

global $woocommerce;

$api = $this->getRazorpayApiInstance();

$attributes = array(
self::RAZORPAY_PAYMENT_ID =>
$_POST[self::RAZORPAY_PAYMENT_ID],
self::RAZORPAY_SIGNATURE => $_POST[self::RAZORPAY_SIGNATURE],
);
$sessionKey = $this->getOrderSessionKey($orderId);
//Check the transient data for razorpay order id, if it's not
available then look into session data.
if(get_transient($sessionKey))
{
$razorpayOrderId = get_transient($sessionKey);
}
else
{
$razorpayOrderId = $woocommerce->session->get($sessionKey);
}

$attributes[self::RAZORPAY_ORDER_ID] = $razorpayOrderId?? '';


rzpLogInfo("verifySignature attr");
rzpLogInfo(json_encode($attributes));
$api->utility->verifyPaymentSignature($attributes);
}

public function rzpThankYouMessage( $thank_you_title, $order )


{
return self::DEFAULT_SUCCESS_MESSAGE;
}

protected function getErrorMessage($orderId)


{
// We don't have a proper order id
rzpLogInfo("getErrorMessage orderId: $orderId");

if ($orderId !== null)


{
$message = 'An error occured while processing this payment';
}
if (isset($_POST['error']) === true && is_array($_POST['error']))
{
$error = $_POST['error'];

$description = htmlentities($error['description']);
$code = htmlentities($error['code']);

$message = 'An error occured. Description : ' . $description .


'. Code : ' . $code;

if (isset($error['field']) === true)


{
$fieldError = htmlentities($error['field']);
$message .= 'Field : ' . $fieldError;
}
}
else
{
$message = 'An error occured. Please contact administrator for
assistance';
}
rzpLogInfo("returning $message");
return $message;
}

/**
* Modifies existing order and handles success case
*
* @param $success, & $order
*/
public function updateOrder(& $order, $success, $errorMessage,
$razorpayPaymentId, $virtualAccountId = null, $webhook = false)
{
global $woocommerce;

$orderId = $order->get_order_number();

rzpLogInfo("updateOrder orderId: $orderId , razorpayPaymentId:


$razorpayPaymentId , success: $success");

if ($success === true)


{
try
{
$wcOrderId = $order->get_id();

$is1ccOrder = get_post_meta( $wcOrderId,


'is_magic_checkout_order', true );

rzpLogInfo("Order details check initiated step 1 for the


orderId: $wcOrderId");

if (is1ccEnabled() && !empty($is1ccOrder) && $is1ccOrder


== 'yes')
{
rzpLogInfo("Order details update initiated step 1 for
the orderId: $wcOrderId");

//To verify whether the 1cc update order function


already under execution or not
if(get_transient('wc_order_under_process_'.$wcOrderId)
=== false)
{
rzpLogInfo("Order details update initiated step 2
for the orderId: $wcOrderId");

$this->update1ccOrderWC($order, $wcOrderId,
$razorpayPaymentId);
}

}
} catch (Exception $e) {
$message = $e->getMessage();
rzpLogError("Failed to update 1cc flow with error :
$message");
}

$payment_method=$order->get_payment_method();

// Need to set the status manually to processing incase of COD


payment method.
if ($payment_method == "cod")
{
$order->update_status( 'processing' );
}
else
{
$order->payment_complete($razorpayPaymentId);
}

if(is_plugin_active('woo-save-abandoned-carts/cartbounty-
abandoned-carts.php')){
handleCBRecoveredOrder($orderId);
}

$order->add_order_note("Razorpay payment successful


<br/>Razorpay Id: $razorpayPaymentId");

if($this->getSetting('route_enable') == 'yes')
{
$razorpayRoute = new RZP_Route_Action();

$wcOrderId = $order->get_id();

$razorpayRoute->transferFromPayment($wcOrderId,
$razorpayPaymentId); // creates transfers from payment
}

if($virtualAccountId != null)
{
$order->add_order_note("Virtual Account Id:
$virtualAccountId");
}

if (isset($woocommerce->cart) === true)


{
$woocommerce->cart->empty_cart();
}
}
else
{
$this->msg['class'] = 'error';
$this->msg['message'] = $errorMessage;

if ($razorpayPaymentId)
{
$order->add_order_note("Payment Failed. Please check
Razorpay Dashboard. <br/> Razorpay Id: $razorpayPaymentId");
}

$order->add_order_note("Transaction Failed:
$errorMessage<br/>");
$order->update_status('failed');
}

if ($webhook === false)


{
$this->add_notice($this->msg['message'], $this->msg['class']);

rzpLogInfo("Woocommerce orderId: $orderId processed through


callback");
}
else
{
rzpLogInfo("Woocommerce orderId: $orderId processed through
webhook");
}
}

public function update1ccOrderWC(& $order, $wcOrderId,


$razorpayPaymentId)
{
global $woocommerce;

$logObj = array();
rzpLogInfo("update1ccOrderWC wcOrderId: $wcOrderId,
razorpayPaymentId: $razorpayPaymentId");
//To avoid the symultanious update from callback and webhook
set_transient('wc_order_under_process_'.$wcOrderId, true, 300);

$api = $this->getRazorpayApiInstance();
$sessionKey = $this->getOrderSessionKey($wcOrderId);

//Check the transient data for razorpay order id, if it's not
available then look into session data.
if(get_transient($sessionKey))
{
$razorpayOrderId = get_transient($sessionKey);
}
else
{
$razorpayOrderId = $woocommerce->session->get($sessionKey);
}

$razorpayData = $api->order->fetch($razorpayOrderId);

$this->updateOrderAddress($razorpayData, $order);

if (empty($razorpayData['promotions'][0]) === false)


{
$couponKey = $razorpayData['promotions'][0]['code'];
}

//Apply coupon to woo-order


if (empty($couponKey) === false)
{
// Remove the same coupon, if already being added to order.
$order->remove_coupon($couponKey);

//TODO: Convert all razorpay amount in paise to rupees


$discount_total = $razorpayData['promotions'][0]['value']/100;

//TODO: Verify source code implementation


// Loop through products and apply the coupon discount
foreach($order->get_items() as $order_item)
{
$total = $order_item->get_total();
$order_item->set_subtotal($total);
$order_item->set_total($total - $discount_total);
$order_item->save();
}
// TODO: Test if individual use coupon fails by hardcoding
here
$isApplied = $order->apply_coupon($couponKey);
$order->save();
rzpLogInfo("Coupon details updated for orderId: $wcOrderId");

//Apply shipping charges to woo-order


if(isset($razorpayData['shipping_fee']) === true)
{

//To remove by default shipping method added on order.


$existingItems = (array) $order->get_items('shipping');
rzpLogInfo("Shipping details updated for orderId: $wcOrderId
is".json_encode($existingItems));

if (sizeof($existingItems) != 0) {
// Loop through shipping items
foreach ($existingItems as $existingItemKey =>
$existingItemVal) {
$order->remove_item($existingItemKey);
}
}

// Get a new instance of the WC_Order_Item_Shipping Object


$item = new WC_Order_Item_Shipping();

// if shipping charges zero


if($razorpayData['shipping_fee'] == 0)
{
$item->set_method_title( 'Free Shipping' );
}
else
{
$isStoreShippingEnabled = "";
$shippingData = get_post_meta( $wcOrderId,
'1cc_shippinginfo', true );

if (class_exists('WCFMmp'))
{
$shippingOptions = get_option(
'wcfm_shipping_options', array());
// By default store shipping should be consider enable
$isStoreShippingEnabled = isset(
$shippingOptions['enable_store_shipping'] ) ?
$shippingOptions['enable_store_shipping'] : 'yes';
}

if ($isStoreShippingEnabled == 'yes')
{
foreach ($shippingData as $key => $value)
{
$item = new WC_Order_Item_Shipping();
//$item->set_method_id($test[$key]['rate_id']);
$item-
>set_method_title($shippingData[$key]['name']);
$item->set_total($shippingData[$key]['price']/100
);
$order->add_item($item);
$item->save();
$itemId = $item->get_id();

$wcfmCommissionOptions = get_option(
'wcfm_commission_options', array() );

$vendorGetShipping = isset(
$wcfmCommissionOptions['get_shipping'] ) ?
$wcfmCommissionOptions['get_shipping'] : 'yes';

if (isset($shippingData[$key]['vendor_id']) &&
$vendorGetShipping == 'yes')
{
$itemData = array(
'method_id' =>
$shippingData[$key]['method_id'],
'instance_id' =>
$shippingData[$key]['instance_id'],
'vendor_id' =>
$shippingData[$key]['vendor_id'],
'Items' =>
$shippingData[$key]['meta_data'][0]['value']
);
updateVendorDetails($shippingData[$key]['price
']/100, $shippingData[$key]['vendor_id'], $wcOrderId);

foreach ($itemData as $itemkey => $itemval)


{
wc_update_order_item_meta( $itemId,
$itemkey, $itemval);
}
}

}
}
else
{
$item = new WC_Order_Item_Shipping();
// if shipping charges zero
if ($razorpayData['shipping_fee'] == 0)
{
$item->set_method_title( 'Free Shipping' );
}
else
{
$item-
>set_method_title($shippingData??[0]['name']);
}

// set an non existing Shipping method rate ID will


mark the order as completed instead of processing status
// $item->set_method_id( "flat_rate:1" );
$item->set_total( $razorpayData['shipping_fee']/100 );

$order->add_item( $item );

$item->save();
}
// Calculate totals and save
$order->calculate_totals();

}
}

// set default payment method


$payment_method = $this->id;
$payment_method_title = $this->title;

// To verify the payment method for particular payment id.


$razorpayPyamentData = $api->payment->fetch($razorpayPaymentId);

$paymentDoneBy = $razorpayPyamentData['method'];

if (($paymentDoneBy === 'cod') && isset($razorpayData['cod_fee'])


== true)
{
$codKey = $razorpayData['cod_fee']/100;
$payment_method = 'cod';
$payment_method_title = 'Cash on delivery';
}

//update payment method title


$order->set_payment_method($payment_method);
$order->set_payment_method_title($payment_method_title);
$order->save();
if (($paymentDoneBy === 'cod') && isset($razorpayData['cod_fee'])
== true)
{
// Get a new instance of the WC_Order_Item_Fee Object
$itemFee = new WC_Order_Item_Fee();

$itemFee->set_name('COD Fee'); // Generic fee name


$itemFee->set_amount($codKey); // Fee amount
// $itemFee->set_tax_class(''); // default for ''
$itemFee->set_tax_status( 'none' ); // If we don't set tax
status then it will consider by dafalut tax class.
$itemFee->set_total($codKey); // Fee amount

// Calculating Fee taxes


// $itemFee->calculate_taxes( $calculateTaxFor );

// Add Fee item to the order


$order->add_item($itemFee);
$order->calculate_totals();
$order->save();
}

//For abandon cart Lite recovery plugin recovery function


if(is_plugin_active( 'woocommerce-abandoned-cart/woocommerce-
ac.php'))
{
$this->updateRecoverCartInfo($wcOrderId);
}

$note = __('Order placed through Razorpay Magic Checkout');


$order->add_order_note( $note );
}

//To update customer address info to wc order.


public function updateOrderAddress($razorpayData, $order)
{
rzpLogInfo("updateOrderAddress function called");
$receipt = $razorpayData['receipt'];

if (isset($razorpayData['customer_details']['shipping_address']))
{
$shippingAddressKey =
$razorpayData['customer_details']['shipping_address'];

$shippingAddress = [];

$shippingAddress['first_name'] = $shippingAddressKey['name'];
$shippingAddress['address_1'] = $shippingAddressKey['line1'];
$shippingAddress['address_2'] = $shippingAddressKey['line2'];
$shippingAddress['city'] = $shippingAddressKey['city'];
$shippingAddress['country'] =
strtoupper($shippingAddressKey['country']);
$shippingAddress['postcode'] = $shippingAddressKey['zipcode'];
$shippingAddress['email'] =
$razorpayData['customer_details']['email'];
$shippingAddress['phone'] = $shippingAddressKey['contact'];

$order->set_address( $shippingAddress, 'shipping' );

$shippingState = strtoupper($shippingAddressKey['state']);
$shippingStateName = str_replace(" ", '', $shippingState);
$shippingStateCode =
getWcStateCodeFromName($shippingStateName);
$order->set_shipping_state($shippingStateCode);

$this->updateUserAddressInfo('shipping_', $shippingAddress,
$shippingStateCode, $order);
rzpLogInfo('shipping details for receipt id: '.$receipt .' is
'. json_encode($shippingAddress));

if
(empty($razorpayData['customer_details']['billing_address']) == false)
{
$billingAddress['first_name'] =
$razorpayData['customer_details']['billing_address']['name'];
$billingAddress['address_1'] =
$razorpayData['customer_details']['billing_address']['line1'];
$billingAddress['address_2'] =
$razorpayData['customer_details']['billing_address']['line2'];
$billingAddress['city'] =
$razorpayData['customer_details']['billing_address']['city'];
$billingAddress['country'] =
strtoupper($razorpayData['customer_details']['billing_address']['country']);
$billingAddress['postcode'] =
$razorpayData['customer_details']['billing_address']['zipcode'];
$billingAddress['email'] =
$razorpayData['customer_details']['email'];
$billingAddress['phone'] =
$razorpayData['customer_details']['billing_address']['contact'];
$order->set_address( $billingAddress, 'billing' );

$billingState =
strtoupper($razorpayData['customer_details']['billing_address']['state']);
$billingStateName = str_replace(" ", '', $billingState);
$billingStateCode =
getWcStateCodeFromName($billingStateName);
$order->set_billing_state($billingStateCode);

$this->updateUserAddressInfo('billing_', $billingAddress,
$billingStateCode, $order);
rzpLogInfo('billing details for receipt id: '.$receipt .'
is '. json_encode($billingAddress));
}
else
{
$order->set_address( $shippingAddress, 'billing' );
$order->set_billing_state($shippingStateCode);

$this->updateUserAddressInfo('billing_', $shippingAddress,
$shippingStateCode, $order);
}

rzpLogInfo("updateOrderAddress function executed");

$order->save();
}
}

/**
* Retrieve a Shipping Zone by it's ID.
*
* @param int $zone_id Shipping Zone ID.
* @return WC_Shipping_Zone|WP_Error
*/
// TODO: can't we directly return the statement?
protected function getShippingZone($zoneId)
{
$zone = WC_Shipping_Zones::get_zone_by('zone_id', $zoneId);

return $zone;
}

// Update user billing and shipping information


protected function updateUserAddressInfo($addressKeyPrefix,
$addressValue, $stateValue, $order)
{
foreach ($addressValue as $key => $value)
{
$metaKey = $addressKeyPrefix;
$metaKey .= $key;

update_user_meta($order->get_user_id(), $metaKey, $value);


}

update_user_meta($order->get_user_id(), $addressKeyPrefix .
'state', $stateValue);
}

// Update Abandonment cart plugin table for recovered cart.


protected function updateRecoverCartInfo($wcOrderId)
{
global $woocommerce;
global $wpdb;

$userId = get_post_meta($wcOrderId, '_customer_user', true);


$currentTime = current_time('timestamp'); // phpcs:ignore
$cutOffTime = get_option('ac_lite_cart_abandoned_time');

if (isset($cut_off_time))
{
$cartCutOffTime = intval($cutOffTime) * 60;
}
else
{
$cartCutOffTime = 60 * 60;
}

$compareTime = $currentTime - $cutOffTime;


if($userId > 0)
{
$userType = 'REGISTERED';
}
else
{
$userType = 'GUEST';
$userId = get_post_meta($wcOrderId, 'abandoned_user_id',
true);
}

$results = $wpdb->get_results( // phpcs:ignore


$wpdb->prepare(
'SELECT * FROM `' . $wpdb->prefix .
'ac_abandoned_cart_history_lite` WHERE user_id = %s AND cart_ignored = %s AND
recovered_cart = %s AND user_type = %s',
$userId,
0,
0,
$userType
)
);
if(count($results) > 0)
{
if(isset($results[0]->abandoned_cart_time) && $compareTime >
$results[0]->abandoned_cart_time)
{
wcal_common::wcal_set_cart_session('abandoned_cart_id_lit
e', $results[0]->id);
}
}

$abandonedOrderId =
wcal_common::wcal_get_cart_session('abandoned_cart_id_lite');

add_post_meta($wcOrderId, 'abandoned_id', $abandonedOrderId);


$wpdb->query( // phpcS:ignore
$wpdb->prepare(
'UPDATE `' . $wpdb->prefix . 'ac_abandoned_cart_history_lite`
SET recovered_cart = %s, cart_ignored = %s WHERE id = %s',
$wcOrderId,
'1',
$abandonedOrderId
)
);
}

protected function handleErrorCase(& $order)


{
$orderId = $order->get_order_number();
rzpLogInfo('handleErrorCase');
$this->msg['class'] = 'error';
$this->msg['message'] = $this->getErrorMessage($orderId);
}

/**
* Add a woocommerce notification message
*
* @param string $message Notification message
* @param string $type Notification type, default = notice
*/
protected function add_notice($message, $type = 'notice')
{
global $woocommerce;
$type = in_array($type, array('notice','error','success'), true) ?
$type : 'notice';
// Check for existence of new notification api. Else use previous
add_error
if (function_exists('wc_add_notice'))
{
wc_add_notice($message, $type);
}
else
{
// Retrocompatibility WooCommerce < 2.1
switch ($type)
{
case "error" :
$woocommerce->add_error($message);
break;
default :
$woocommerce->add_message($message);
break;
}
}
}

/**
* Fetching version info for woo-razorpay and woo-razorpay-
subscription
* Which will be sent through checkout as meta info
* @param $data
* @return array
*/
public function getVersionMetaInfo($data)
{
if (isset($data['subscription_id']) && isset($data['recurring']))
{
$pluginRoot = WP_PLUGIN_DIR . '/razorpay-subscriptions-for-
woocommerce';
return array(
'integration' => 'woocommerce-subscription',
'integration_version' => get_plugin_data($pluginRoot .
'/razorpay-subscriptions.php')['Version'],
'integration_woo_razorpay_version' =>
get_plugin_data(plugin_dir_path(__FILE__) . 'woo-razorpay.php')['Version'],
'integration_parent_version' => WOOCOMMERCE_VERSION,
);
} else {
return array(
'integration' => 'woocommerce',
'integration_version' =>
get_plugin_data(plugin_dir_path(__FILE__) . 'woo-razorpay.php')['Version'],
'integration_parent_version' => WOOCOMMERCE_VERSION,
);
}
}
}

//update vendor data into wp_wcfm_marketplace_orders


function updateVendorDetails($shippingFee, $vendorId, $orderId)
{
global $woocommerce;
global $wpdb;
$commission = $wpdb->get_results(
$wpdb->prepare(
'SELECT * FROM `' . $wpdb->prefix . 'wcfm_marketplace_orders`
WHERE vendor_id = %d AND order_id = %d',
$vendorId,
$orderId
)
);

if (count($commission) > 0)
{
$totalComm = $commission[0]->total_commission+$shippingFee;
$wpdb->query(
$wpdb->prepare(
'UPDATE `' . $wpdb->prefix . 'wcfm_marketplace_orders` SET
shipping = %d, total_commission = %d WHERE vendor_id = %d AND order_id = %d',
$shippingFee,
$totalComm,
$vendorId,
$orderId
)
);
}
}

/**
* Add the Gateway to WooCommerce
**/
function woocommerce_add_razorpay_gateway($methods)
{
$methods[] = 'WC_Razorpay';
return $methods;
}

add_filter('woocommerce_payment_gateways',
'woocommerce_add_razorpay_gateway');

/**
* Creating the settings link from the plugins page
**/
function razorpay_woo_plugin_links($links)
{
$pluginLinks = array(
'settings' => '<a href="'. esc_url(admin_url('admin.php?page=wc-
settings&tab=checkout&section=razorpay')) .'">Settings</a>',
'docs' => '<a href="https://razorpay.com/docs/payment-
gateway/ecommerce-plugins/woocommerce/woocommerce-pg/">Docs</a>',
'support' => '<a
href="https://razorpay.com/contact/">Support</a>'
);

$links = array_merge($links, $pluginLinks);

return $links;
}

add_filter('plugin_action_links_' . plugin_basename(__FILE__),
'razorpay_woo_plugin_links');
}

// This is set to a priority of 10


function razorpay_webhook_init()
{
$rzpWebhook = new RZP_Webhook();

$rzpWebhook->process();
}

define('RZP_PATH', plugin_dir_path( __FILE__ ));


define('RZP_CHECKOUTJS_URL', 'https://checkout.razorpay.com/v1/checkout.js');
define('RZP_1CC_CSS_SCRIPT', 'RZP_1CC_CSS_SCRIPT');

function enqueueScriptsFor1cc()
{
$siteurl = get_option('siteurl');

$domain = parse_url($siteurl, PHP_URL_HOST);


$domain_ip = gethostbyname($domain);

//Consider https if site url is not localhost server.


if (filter_var($domain_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 |
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))
{
$siteurl = str_replace('http://', 'https://', $siteurl);
}

wp_register_script('1cc_razorpay_checkout', RZP_CHECKOUTJS_URL, null,


null);
wp_enqueue_script('1cc_razorpay_checkout');
wp_register_style(RZP_1CC_CSS_SCRIPT, plugin_dir_url(__FILE__) .
'public/css/1cc-product-checkout.css', null, null);
wp_enqueue_style(RZP_1CC_CSS_SCRIPT);

wp_register_script('btn_1cc_checkout', plugin_dir_url(__FILE__) . 'btn-


1cc-checkout.js', null, null);
wp_localize_script('btn_1cc_checkout', 'rzp1ccCheckoutData', array(
'nonce' => wp_create_nonce("wp_rest"),
'siteurl' => $siteurl,
'blogname' => get_bloginfo('name'),
'cookies' => $_COOKIE,
'requestData' => $_REQUEST,
) );
wp_enqueue_script('btn_1cc_checkout');
}

//To add 1CC button on cart page.


add_action( 'woocommerce_proceed_to_checkout', 'addCheckoutButton');

function addCheckoutButton()
{
add_action('wp_enqueue_scripts', 'enqueueScriptsFor1cc', 0);

if (isRazorpayPluginEnabled() && is1ccEnabled() )


{
if (isTestModeEnabled()) {
$current_user = wp_get_current_user();
if ($current_user->has_cap( 'administrator' ) || preg_match(
'/@razorpay.com$/i', $current_user->user_email )) {
$tempTest = RZP_PATH . 'templates/rzp-cart-checkout-btn.php';
load_template( $tempTest, false, array() );
}
} else {
$tempTest = RZP_PATH . 'templates/rzp-cart-checkout-btn.php';
load_template( $tempTest, false, array() );
}
}
else
{
return;
}
}

//To add 1CC Mini cart checkout button


if(isRazorpayPluginEnabled() && is1ccEnabled() && isMiniCartCheckoutEnabled())
{
add_action( 'woocommerce_widget_shopping_cart_buttons', function()
{
// Removing Buttons
remove_action( 'woocommerce_widget_shopping_cart_buttons',
'woocommerce_widget_shopping_cart_proceed_to_checkout', 20 );

add_action('woocommerce_cart_updated', 'enqueueScriptsFor1cc', 10);

add_action( 'woocommerce_widget_shopping_cart_buttons',
'addMiniCheckoutButton', 20 );
}, 1 );
}

function addMiniCheckoutButton()
{
add_action('wp_enqueue_scripts', 'enqueueScriptsFor1cc', 0);

if (isTestModeEnabled()) {
$current_user = wp_get_current_user();
if ($current_user->has_cap( 'administrator' ) || preg_match(
'/@razorpay.com$/i', $current_user->user_email )) {
$tempTest = RZP_PATH . 'templates/rzp-mini-checkout-btn.php';
load_template( $tempTest, false, array() );
}
} else {
$tempTest = RZP_PATH . 'templates/rzp-mini-checkout-btn.php';
load_template( $tempTest, false, array() );
}

//To add 1CC button on product page.


if(isRazorpayPluginEnabled() && is1ccEnabled() && isPdpCheckoutEnabled())
{
add_action( 'woocommerce_after_add_to_cart_button',
'addPdpCheckoutButton');
}

function addPdpCheckoutButton()
{
add_action('wp_enqueue_scripts', 'enqueueScriptsFor1cc', 0);

if (isTestModeEnabled()) {
$current_user = wp_get_current_user();
if ($current_user->has_cap( 'administrator' ) || preg_match(
'/@razorpay.com$/i', $current_user->user_email )) {
$tempTest = RZP_PATH . 'templates/rzp-pdp-checkout-btn.php';
load_template( $tempTest, false, array() );
}
} else {
$tempTest = RZP_PATH . 'templates/rzp-pdp-checkout-btn.php';
load_template( $tempTest, false, array() );
}
}

// for admin panel custom alerts


function addAdminSettingsAlertScript()
{
if (isRazorpayPluginEnabled()) {
wp_enqueue_script('rzpAdminSettingsScript', plugin_dir_url(__FILE__)
.'public/js/admin-rzp-settings.js');
}
}

add_action('admin_enqueue_scripts', 'addAdminSettingsAlertScript');

function disable_coupon_field_on_cart($enabled)
{
if (isTestModeEnabled()) {
$current_user = wp_get_current_user();
if ($current_user->has_cap( 'administrator' ) || preg_match(
'/@razorpay.com$/i', $current_user->user_email )) {
if (is_cart()) {
$enabled = false;
}
}
} else {
if (is_cart()) {
$enabled = false;
}
}
return $enabled;
}

if(is1ccEnabled())
{
add_filter('woocommerce_coupons_enabled', 'disable_coupon_field_on_cart');
add_action('woocommerce_cart_updated', 'enqueueScriptsFor1cc', 10);
add_filter('woocommerce_order_needs_shipping_address', '__return_true');
}

// instrumentation

// plugin activation hook


function razorpayPluginActivated()
{
$paymentSettings = get_option('woocommerce_razorpay_settings');
$trackObject = new TrackPluginInstrumentation($paymentSettings['key_id'],
$paymentSettings['key_secret']);

$activateProperties = [
'page_url' => $_SERVER['HTTP_REFERER'],
'redirect_to_page' => $_SERVER['REQUEST_SCHEME'] . '://' .
$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']
];

$response = $trackObject->rzpTrackSegment('plugin activate',


$activateProperties);
}

// plugin deactivation hook


function razorpayPluginDeactivated()
{
global $wpdb;
$isTransactingUser = false;

$paymentSettings = get_option('woocommerce_razorpay_settings');

$trackObject = new TrackPluginInstrumentation($paymentSettings['key_id'],


$paymentSettings['key_secret']);

$rzpTrancationData = $wpdb->get_row($wpdb->prepare("SELECT post_id FROM


$wpdb->postmeta AS P WHERE meta_key = %s AND meta_value = %s",
"_payment_method", "razorpay"));

$arrayPost = json_decode(json_encode($rzpTrancationData), true);

if (empty($arrayPost) === false and


($arrayPost == null) === false)
{
$isTransactingUser = true;
}

$deactivateProperties = [
'page_url' => $_SERVER['HTTP_REFERER'],
'is_transacting_user' => $isTransactingUser
];

$response = $trackObject->rzpTrackSegment('plugin deactivate',


$deactivateProperties);
}

// plugin upgrade hook


function razorpayPluginUpgraded()
{
$paymentSettings = get_option('woocommerce_razorpay_settings');

$trackObject = new TrackPluginInstrumentation($paymentSettings['key_id'],


$paymentSettings['key_secret']);

$upgradeProperties = [
'page_url' => $_SERVER['HTTP_REFERER'],
'prev_version' =>
get_option('rzp_woocommerce_current_version'),
'new_version' => get_plugin_data(__FILE__)['Version'],
];

$response = $trackObject->rzpTrackSegment('plugin upgrade',


$upgradeProperties);

if ($response['status'] === 'success')


{
$existingVersion = get_option('rzp_woocommerce_current_version');

if(isset($existingVersion))
{
update_option('rzp_woocommerce_current_version',
get_plugin_data(__FILE__)['Version']);
}
else
{
add_option('rzp_woocommerce_current_version',
get_plugin_data(__FILE__)['Version']);
}
}
}

//Changes Recovery link URL to Magic cart URL to avoid redirection to checkout
page
function cartbounty_alter_automation_button( $button ){
return str_replace("cartbounty=","cartbounty=magic_",$button);
}

if(is_plugin_active('woo-save-abandoned-carts/cartbounty-abandoned-
carts.php')){
add_filter( 'cartbounty_automation_button_html',
'cartbounty_alter_automation_button' );
}
RESULTS

1. Hostinger Server

This is the server where the website is hosted represented as the following:
The PHP files connected to the database:
2. User Roles
2.1. Dashboard

2.2. Plugins
2.3. Products
2.4. Customers

2.5. Front-End
3. Database: PhpMyAdmin

4. Mailing System: Round Cube


SYSTEM TESTING
The aim of the system testing process was to determine all defects in our project. The program
was subjected to a set of test inputs and various observations were made and based on these
observations it will be decided whether the program behaves as expected or not.

eCommerce testing helps in the prevention of errors and adds value to the product by
ensuring conformity to client requirements. The objective of testing is to ensure

• Software reliability
• Software quality
• System Assurance
• Optimum performance and capacity utilization

Setting up an E-commerce system is a complex process and subject to many market-specific


variables. To maintain the integrity of the E Commerce system, testing becomes compulsory.

Our Project went through two levels of testing

1. Unit Testing 2. Integration Testing

UNIT TESTING
Unit testing is undertaken when a module has been created and successfully reviewed. In
order to test a single module, we need to provide a complete environment i.e., besides the
module we would require
• The procedures belonging to other modules that the module under test calls
• Non local data structures that module accesses
• A procedure to call the functions of the module under test with appropriate
parameters

1. Test For the admin module


Testing admin login form - This form is used for log in of administrator of the system. In
this we enter the username and password if both are correct administration page will
open otherwise if any of data is wrong it will get redirected back to the login page and
again ask for username and password
Customer account addition - In this section the admin can verify customer details from
data info and then only add customer details to main library database it contains add and
delete buttons if user click add button data will be added to customer database and if he
clicks delete button the customer data will be deleted
2. Test for Student login module
Test for Customer login Form - This form is used for log in of customer. In this we enter
the username and password if these both are correct customer login page will open
otherwise if any of data is wrong it will get redirected back to the login page and again ask
for username and password.
Test for account creation - This form is used for new account creation when customer
does not fill the form completely it asks again to fill the whole form when he fills the form
fully it gets redirected to page which show waiting for conformation message as his data
will be only added by administrator after verification.

INTEGRATION TESTING
In this type of testing, we test various integration of the project module by providing the
input. The primary objective is to test the module interfaces in order to ensure that no errors
are occurring when one module invokes the other module.
Integration testing is the second level of the software testing process comes after unit testing.
In this testing, units or individual components of the software are tested in a group. The focus
of the integration testing level is to expose defects at the time of interaction between
integrated components or units.
Unit testing uses modules for testing purpose, and these modules are combined and tested
in integration testing. The Software is developed with a number of software modules that are
coded by different coders or programmers. The goal of integration testing is to check the
correctness of communication among all the modules.
Once all the components or modules are working independently, then we need to check the
data flow between the dependent modules is known as integration testing.
Guidelines for Integration Testing
• We go for the integration testing only after the functional testing is completed on each
module of the application.
• We always do integration testing by picking module by module so that a proper
sequence is followed, and also, we don't miss out on any integration scenarios.
• First, determine the test case strategy through which executable test cases can be
prepared according to test data.
• Examine the structure and architecture of the application and identify the crucial
modules to test them first and also identify all possible scenarios.
• Design test cases to verify each interface in detail.
• Choose input data for test case execution. Input data plays a significant role in testing.
• If we find any bugs then communicate the bug reports to developers and fix defects
and retest.
• Perform positive and negative integration testing.

Types of Integration Testing


Integration testing can be classified into two parts:
• Incremental integration testing
• Non-incremental integration testing
Types of Testing for our E-commerce System:
1. Browser compatibility
• Lack of support for early browsers
• Browser specific extensions
• Browser testing should cover the main platforms (Linux, Windows, Mac etc.)

2. Page display
• Incorrect display of pages
• Runtime error messages
• Poor page download time
• Dead hyperlink, plugin dependency, font sizing, etc.

3. Session Management
• Session Expiration
• Session storage

4. Usability
• Non-intuitive design
• Poor site navigation
• Catalog navigation
• Lack of help-support

5. Content Analysis
• Misleading, offensive and litigious content
• Royalty free images and copyright infringement
• Personalization functionality
• Availability 24/7

6. Availability
• Denial of service attacks
• Unacceptable levels of unavailability

7. Back-up and Recovery


• Failure or fall over recovery
• Backup failure
• Fault tolerance
8. Transactions
• Transaction Integrity
• Throughput
• Auditing

9. Shopping order processing and purchasing


• Shopping cart functionality
• Order processing
• Payment processing
• Order tracking

10. Operational business procedures


• How well e-procedure copes
• Observe for bottlenecks

11. System Integration


• Data Interface format
• Interface frequency and activation
• Updates
• Interface volume capacity
• Integrated performance

12. Performance
• Performance bottlenecks
• Load handling
• Scalability analysis

13. Login and Security


• Login capability
• Penetration and access control
• Insecure information transmission
• Web attacks
• Computer viruses
• Digital signatures

Challenges of E-commerce Testing

• Compliance with security guidelines to safeguard customer data and identity


• Compliance with accessibility standards to support multi-lingual markets and business
regions
• End to end testing and test management for large e-commerce transformation programs
• Scalability and reliability of applications
SCREENSHOTS OF THE WEBSITE
Snacks Mania

Variety of Sharbat
Sweet Delicacies

Aachari Maza
Product Page
Product Page
Cart Page

Checkout Page
Payment Gateway – Razorpay
CONCLUSION

After completing this “Meena’s Kitchen - An eCommerce website” project, I can now look
back and see how much I have learnt and grown as a software engineer. As a future
graduating student from JECRC University, I got a vision that incorporates values based on the
perception of development, progress and continuous improvement. Hence, my leadership
within the student activities office was considered as a crucial factor in acquiring creativity,
organization and management.

Throughout this eCommerce website project, there were many constraints encountered. The
key one that picked my interest is that most implemented approaches demand the right
behaviour of the user.

Finally, I can say that working with a Content Management System which was WordPress in
the project was a skilful experience for me. It was an opportunity that has long term
professional and personal goals, by learning new technologies and tools on a practical level.
Perhaps the most important thing that I have learnt through this project is the following: As
a software engineer, I should adapt to the needs of the market/client, not try to impose my
own vision of how things should be. Indeed, it is constant adaptability that is the hallmark of
a true engineer, as Carl Sagan said “Science is a wa of thinking, much more than it is a body
of knowledge”.
BIBLIOGRAPHY

1. WordPress.org (https://wordpress.org/)

2. Razorpay for WooCommerce (https://wordpress.org/plugins/woo-razorpay/)

3. WooCommerce (https://woocommerce.com/)

4. Hostinger (https://www.hostinger.in/)

5. phpMyAdmin (https://www.phpmyadmin.net/)

6. GeneratePress – Theme (https://generatepress.com/)

7. Guru99 (https://www.guru99.com/)

8. WPBeginner (https://www.wpbeginner.com/)

9. W3Schools (https://www.w3schools.com/)

10. YoastSEO (https://yoast.com/wordpress/plugins/seo/)

You might also like