اذهب إلى المحتوى

بعد أن غطينا معظم ما يتعلق بالواجهات البرمجية وكيفية عملها، مرورًا بالتعرف على الواجهة البرمجية لمنصة زد، سنعمل على بناء تطبيق يتصل بالواجهة البرمجية لمنصة زد ويوسع من عمل المتاجر المبنية بتلك المنصة بإضافة ميزة لها وهي زيادة المبيعات بزيادة الاحتفاظ العملاء وكسب ولائهم.

هذا المقال هو جزء من سلسلة مقالات حول الواجهة البرمجية API وكيفية الاستفادة منها في بناء تطبيق ويب:

  1. مدخل إلى الواجهات البرمجية API
  2. الاتصال بواجهة زد البرمجية وفهم عملية الاستيثاق والتصريح
  3. أمثلة عملية لاستخدام واجهة برمجة متاجر زد zid API
  4. تطوير تطبيق عملي يزيد من احتفاظ العملاء عبر واجهة زد البرمجية

فكرة التطبيق

فكرة التطبيق سهلة وبسيطة، بحيث سنستخدم نقطة الوصول الخاصة بسلات الشراء المتروكة لاستعراض العملاء الذين تركوا سلة ما دون إتمام عملية الشراء لمتجر ما ومن ثم سنولد عبر نقطة وصول القسائم قسيمة تخفيض Coupons ونرسلها عبر البريد اﻹلكتروني للعملاء لحضهم على استعمال قسيمة التخفيض وإتمام عملية الشراء. يُحدد المتجر بالمفتاح X-Manager-Token الذي سيزودنا به صاحب المتجر الذي يريد الاستفادة من هذا التطبيق وسنستعمل مفتاح متجر تجريبي أثناء عملية التطوير.

سيكون بإمكانك استغلال هذا التطبيق وبناء منصة متكاملة لزيادة عملاء متاجر زد الإلكترونية، وبالتالي زيادة المداخيل السلبية Passive Income لأصحاب المتاجر، الأمر الذي يحقق الفائدة والنفع لهم.

المتطلبات المسبقة لبناء التطبيق

سنبني التطبيق باستخدام تقنية NodeJS كنظام خلفي Backend، مع واجهة مستخدم بسيطة تكفينا فقط ﻻستخدام سهل وسلس للتطبيق، وسنبني التطبيق باستخدام محرك القوالب Template Engine المعروف باسم Pug.

يُفترض أن لديك معرفةً مسبقةً ولو قليلةً بالبرمجة باستخدام NodeJS وإطار العمل ExpressJS لهذا لن نتطرق إلى شرحهما. يمكنك استخدام مدير الحزم Package Manager الذي تفضله سواءً NPM أو YARN، سأستخدم YARN في هذا المشروع.

تهيئة المشروع

لبداية مشروع جديد، يكفي أن تفتح سطر أوامر وتكتب الأمر التالي:

yarn init

سيسألك مدير الحزم عن معلومات التطبيق الذي تريد إنشاؤه، يكفي أن تجيب بالقبول عن كل الأسئلة دون تعديل. بعدها، سينشئ مدير الحزم ملفًا باسم package.json بهذا المحتوى:

{
  "name": "zid",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}

الآن سنثبت عددًا من الإضافات اللازمة لعمل التطبيق بصورة طبيعية، وسنبدأ بتثبيت إطار العمل ExpressJS عن طريق تنفيذ الأمر التالي في سطر الأوامر:

yarn add express

بعد انتهاء مدير الحزم من تثبيت ExpressJS، سنثبت مكتبة Nodemon والتي تمكننا من تشغيل خادم دائم للتطبيق بحيث يعيد Nodemon تشغل الخادم آليًا في كل مرة نعدل على شفرة التطبيق.

مكتبة Nodemon تستخدم فقط في مرحلة التطوير وليس في البيئة الإنتاجية، لهذا يجب علينا تثبيتها على هذا الأساس، ويكون ذلك باستخدام هذا الأمر:

yarn add nodemon --dev

الآن، الملف package.json سيظهر كالتالي:

{
  "name": "zid",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.7"
  }
}

الآن أنشئ ملفًا باسم nodemon.json، وهو ملف خاص بإعدادات Nodemon ويجب أن يحتوي على الشفرة التالية:

{
    "ext": "js",
    "exec": "node index.js"
}

السطر اﻷول، يعني أن على Nodemon أن يتتبع التغيرات الحاصلة على أي ملف بامتداد js. والسطر الثاني يمثل الأمر المراد تنفيذه عند حصول أي تغيير على أي ملف بالامتداد المحدد.

إنشاء أول نقطة وصول من الواجهة الخلفية

الآن، دعنا ننشئ أولى نقاط الوصول Endpoint الخاصة بنا.

أولًا، نعدّل على ملف package.json ونضيف له سكريبت كالتالي

"scripts": {
    "start": "nodemon"
  }

سيكون الملف النهائي كالتالي:

{
  "name": "zid",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.7"
  },
"scripts": {
    "start": "nodemon"
  }
}

الآن كل شيء جاهز. ننشئ ملف جديد باسم index.js يحتوي على الشفرة التالية:

import express from "express";

var app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`Server started on ${port}`);
});

سنشرح هذه الشفرة المصدرية لأنها أساس كل ما سيأتي لاحقًا.

السطرين الأول والثاني:

import express from "express";

var app = express();

استدعينا إطار العمل express، بعدها أنشأنا نسخة منه Instance منه باسم app، ومنها سيعمل التطبيق ككل.

السطرين الثالث والرابع تخص إطار Express إذ أخبرنا ببساطة Express بأن كل الطلبات Requests ستكون على شكل JSON.

السطر الخامس:

const port = process.env.PORT || 3000;

أنشأنا ثابتًا يحدد رقم المنفذ Port الذي سيعمل عليه الخادم الخاص بنا. بحيث إن كان معرفا كمتغير نظام أو سيؤخذ المنفذ 3000.

السطر السادس هو إنشاء الخادم الخاص بالتطبيق والذي سيعمل على المنفذ Port الذي حددناه سابقًا:

app.listen(port, () => {
  console.log(`Server started on ${port}`);
});

الآن أضف الشفرة المصدرية التالية مباشرةً بعد السطر الرابع:

app.get("/", (req, res) => {
  res.send(‘Hello Hsoub’);
});

سيكون شكل الملف كالتالي:

import express from "express";

var app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.get("/", (req, res) => {
  res.send(`Hello Hsoub`);
});

const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`Server started on ${port}`);
});

سنثبت بعض المكتبات التي سنستخدمها في بناء التطبيق الخاص بنا. نفِّذ الأمر التالي في سطر الأوامر في مجلد المشروع:

yarn add axios pug

ثبتنا المكتبتين التاليتين:

  • Axios: هو مكتبة مهمتها القيام بإرسال طلبيات HTTP.
  • Pug: مكتبة تخص قوالب لبناء واجهات المستخدم Frontend Templating Engine.

هنالك عدة مكتبات تخص بناء واجهات المستخدم، وقع اﻹختيار على مكتبة Pug بسبب أن الشيفرة البرمجية الخاصة بها بسيطة وسهلة القراءة، باﻹضافة إلى أنها متوافقة بامتياز مع إطار العمل ExpressJS.

الآن نفّذ الأمر:

yarn start

ستظهر لك رسالة خطأ تفيد بأنه ﻻ يمكنك استيراد إطار العمل express داخل المشروع عبر import، ويكفي لحل هذه المشكلة فقط أن تضيف السطر التالي إلى الملف package.json:

  "type": "module",

في حالة واجهت رسالة خطأ مثل الرسالة التالية:

Error: listen EADDRINUSE: address already in use :::3000

تأكد من إيقاف التطبيقات الأخرى التي تستخدم المنفذ 3000 أو غير المنفذ الخاص بالتطبيق ليكون مثلا 3001:

افتح المتصفح، وادخل العنوان التالي:

http://localhost:3000

يفترض أن تحصل على الرسالة التالية:

First_Endpoint.png

تهانينا، أتممت تهئية التطبيق وإنشاء أولى نقاط الوصول Endpoint الخاصة بالواجهة الخلفية فيه باستخدام NodeJS وExpressJS.

بناء تطبيق الاحتفاظ بمستخدمي متاجر زد

الآن وقد شرحنا باختصار شديد طريقة بناء واجهة خلفية برمجية بسيطة باستخدام تقنية NodeJS وإطار عمل ExpressJS، سننتقل إلى بناء تطبيق بسيط لزيادة ولاء العملاء.

قبل أن نبدأ، عليك أن تحاول الاستزادة قدر الإمكان في ما يتعلق بالبرمجة باستخدام NodeJS، وطريقة بناء الواجهات البرمجية، لأن هدفنا هنا هو التطبيق، لهذا لن تجدني أشرح باستفاضة الأمور البسيطة.

جلب سلات الشراء المتروكة

أنشئ ملفًا جديدًا باسم zid.js يحوي على الشفرة المصدرية التالية:

//  axios استيراد مكتبة 
import axios from "axios";
// إنشاء متغير يحمل رابط الواجهة البرمجية
const ZidAPI = "https://api.zid.dev/app/v1";
const AbandonedCarts = async () => {
  const headers = {
    "X-MANAGER-TOKEN": process.env.MANAGER_TOKEN,
    Authorization: "Bearer " + process.env.auth,
  };
  try {
    const getAbandondCarts = await axios.get(
      `${ZidAPI}/managers/store/abandoned-carts`,
      {
        headers: headers,
        params: { page: 1, page_size: 10, duration: 4 },
      }
    );
    if (getAbandondCarts) {
      return getAbandondCarts.data;
    }
  } catch (error) {
    return error;
  }
};

export default AbandonedCarts;

والآن، لنشرح الشيفرة باختصار شديد. بدايةً، استدعينا المكتبة axios من أجل إرسال بطلبات للواجهة البرمجية الخاصة بمنصة زد، ثم أنشأنا ثابتًا باسم ZidAPI ويحوي أساس نقطة الوصول الخاصة بالواجهة البرمجية لمنصة زد.

أنشأنا دالة Function غير متزامنة باسم AbandonedCarts ومهمتها هي جلب سلات الشراء المتروكة على متجرنا.

أنشأنا داخل تلك الدالة ثابتًا باسم headers، وهو كائن يحتوي على قيمتين مفتاح المتجرX-MANAGER-TOKEN و مفتاح الواجهة البرمجية Authorization. إن لم تعرف من أين حصلنا على هذين المتغيرين فارجع إلى المقالات السابقة.

قد أجدك تتساءل ما معنى process.env؟ هي طريقة لحماية مفاتيح الوصول، وذلك عن طريق إضافة المفتاح كمتغير بيئة Environment Variable. فمثلا، في تطبيقنا هذا، حمينا مفتاح الوصول الخاص بالمتجر باسم X-MANAGER-TOKEN.

تُنفّذ هذه العملية على نحو فتح سطر الأوامر وإدخال الأمر التالي:

export X-MANAGER-TOKEN=value

استبدل value بقيمة مفتاح المتجر بدون إضافة مسافة بعد علامة = ونفس الشيء بالنسبة للمتغير Authorization.

اقتباس

ملاحظة: هذه الطريقة تعمل على أنظمة التشغيل لينكس وماكنتوش، أما في نظام التشغيل ويندوز، فمن الأفضل تثبيت WSL 2 أو استخدام الطريقة المعروفة لانشاء متغيرات النظام.

سنشرح في عجالة طريقة تثبيت WSL2 على نظام التشغيل ويندوز، ومن ثم طريقة إنشاء متغيرات النظام.

بداية تأكد من تحديث نظام التشغيل عندك إلى آخر نسخة ممكنة حتى تستطيع الاستفادة من خاصية تثبيت نظام لينكس كنظام ثانوي داخل ويندوز.

افتح PowerShell كمستخدم مدير، ونفذ الأمر التالي:

dism.exe /online /enable-feature/featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

بعد انتهاء تنفيذ الأمر، نحتاج الآن إلى تفعيل خاصية الأنظمة الوهمية Virtual Machine الخاصة بنظام التشغيل ويندوز وذلك عن طريق تنفيذ الأمر التالي في نفس نافذة PowerShell السابقة:

dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

بعد انتهاء تنفيذ الأمر، أعد تشغيل جهازك حتى يتسنى للنظام تفعيل جميع الخصائص المطلوبة.

علينا الآن تحديث نواة نظام التشغيل لينكس، وذلك بتنزيل آخر تحديث متوفر، بعد انتهاء التنزيل، افتح الملف واتبع الخطوات لتثبيته كأي برنامج آخر.

إفتح نافذة PowerShell، ونفذ الأمر التالي:

wsl --set-default-version 2

افتح متجر تطبيقات ويندوز وابحث عن Ubuntu وثبِّته مثل أي تطبيق آخر، وبعد الانتهاء أعد تشغيل جهازك. بإمكانك الآن تنفيذ أوامر لينكس داخل نظام ويندوز دون مشاكل. لمزيد من التفاصيل، ارجع إلى مقال تطبيق Hyper-V في ويندوز 10.

على أي حال، هنالك طريقة أخرى ﻹضافة متغيرات النظام عن طريق مكتبة dotenv، وتكون كالتالي:

في سطر اﻷوامر أدخل الأمر التالي:

yarn add dotenv

أنشئ ملفًا باسم env. داخل المجلد الرئيسي للتطبيق، وضع بداخله قيمتي مفتاح المتجر ومفتاح الواجهة البرمجية، النتيجة ستكون كالتالي:

MANAGER_TOKEN=value
auth=value

لا تنسى تبديل القيمة value ووضع المفتاح المناسب الخاص بك مكانها.

DotEnv.png

في بداية الملف index.js نضيف الشيفرة البرمجية التالية:

import dotenv from "dotenv";

بقي علينا اﻵن أن نضيف شيفرة تخبر إطار Express أننا سنستخدم مكتبة dotenv لجلب متغيرات البيئة من الملف ‎.env عوض استخدام المتغيرات المخزنة فعلا في نظام التشغيل.

في ملف index.js وقبل السطر التالي:

var app = express();

نضيف ما يلي:

dotenv.config();

بإمكانك اﻵن إضافة متغيرات النظام مباشرةً للملف env. دون إضافتها بصورة مباشرة إلى نظام التشغيل.

بعد الانتهاء من ضبط متغيرات البيئة، أنشأنا كتلة برمجية باستخدام Try-Catch لتجريب الإتصال بالواجهة البرمجية لمنصة زد.

أنشأنا متغيرًا باسم getAbandondCarts وأسندنا إليه القيمة الراجعة من إرسال الطلب إلى الواجهة البرمجية لمنصة زد. بحيث أرسلنا طلب Request من نوع GET، يحمل المعلومات المطلوبة في توثيق الواجهة البرمجية، وهي ترويسة الطلب headers وأيضا استعلامًا Query فيه عدد الصفحات وأيضا عدد العناصر في الصفحة.

بعدها تحققنا إن استقبلنا إجابة خادم الواجهة البرمجية لمنصة زد، وفي حالة وجود الإجابة، فإننا نرجع المحتوى خاصتها؛ أما في حالة وجود خطأ، فسننتقل إلى الجزء catch والذي سيرجع قيمة الخطأ مثل رسالة.

في آخر الملف، صدرنا الدالة لنستخدمها في مواضع أخرى.

الآن ننتقل إلى الملف index.js ونستدعي الملف zid.js بالطريقة التالية:

import AbandonedCarts from "./zid.js";

ثم نضيف نقطة وصول جديدة بنفس الطريقة السابقة:

app.get("/carts", async (req, res) => {
  const response = await AbandonedCarts();
  const renderedData = response["abandoned-carts"];
  res.send(renderedData);
});

دعنا نفهم الشيفرة السابقة:

  • أنشأنا نقطة وصول جديدة بحيث تستقبل طلبات من نوع GET
  • أنشأنا ثابتًا جديدًا باسم response بحيث يستقبل إجابة الخادم من الدالة التي أنشأناها سابقًا باسم AbandonedCarts
  • أنشأنا ثابتًا جديدًا باسم renderedData يحمل قيمة فرعية من الثابت response
  • أعدنا محتوى الثابت renderedData

لنجرب الآن نقطة الوصول الجديدة، افتح برنامج Insomnia، وأنشئ طلبًا جديدًا من نوع GET لنقطة الوصول التالية:

http://localhost:3000/carts

وأرسل الطلب، إن لم توجد سلات شراء متروكة، فإن الواجهة البرمجية لمنصة زد، ستعيد لنا مصفوفة فارغة، أما إن وجدت سلات شراء متروكة، ستحصل على مصفوفة مشابهة لهذه:

[
  {
    "id": "6718344e-8e2c-4667-b2cd-1178632de6e9",
    "store_id": "a739c51c-8103-4648-873b-cc3a8ea2dc8a",
    "session_id": "xQH4imSzunyafhd2QkD2llkjz8qjEKvW",
    "cart_id": "904142370",
    "order_id": 7231078,
    "phase": "completed",
    "customer_id": 5835,
    "customer_name": "man",
    "customer_email": "m.a***at@zid.sa",
    "customer_mobile": "966550*****",
    "city_id": 1,
    "products_count": 1,
    "reminders_count": 0,
    "cart_total": 138,
    "cart_total_string": "138.00 SAR",
    "created_at": "2021-05-20 08:59:54",
    "updated_at": "2021-05-20 10:59:35"
  },
  {
    "id": "33e932a1-0a9f-4468-a8de-c09540f9aa56",
    "store_id": "a739c51c-8103-4648-873b-cc3a8ea2dc8a",
    "session_id": "b6fRKKmcwVtLssqQNgvPSEaY2FvzAG7B",
    "cart_id": "914265844",
    "order_id": null,
    "phase": "shipping_address",
    "customer_id": 3450024,
    "customer_name": "Mohammad",
    "customer_email": "asha.k@fast.com",
    "customer_mobile": "966506766***",
    "city_id": 1,
    "products_count": 2,
    "reminders_count": 0,
    "cart_total": 504.85,
    "cart_total_string": "504.85 SAR",
    "created_at": "2021-05-19 09:09:45",
    "updated_at": "2021-05-19 12:23:16"
  }
]
اقتباس

ملاحظة: لقد أُخفيت أسماء العملاء حفاظا على الخصوصية.

أصبحنا الآن قادرين على تتبع كل السلات المتروكة وحالتها والتفاصيل الخاصة بها وفعل أي شيء متاح معها عبر هذه التفاصيل مع كل سلة وهنا مربط الفرس.

سننتقل الآن إلى إنشاء واجهة استخدام بسيطة باستخدام محرك القوالب Pug.

بناء شريط التنقل وواجهة السلات المتروكة

سنبني واجهات التطبيق عبر محركة القوالب Pug وسنبدأ بواجهة السلات المتروكة، ولكن دعنا أول أن نفهم ما هو محرك القوالب.

محرك القوالب Template Engine هو نظام قوالب يسمح لنا بإنشاء واجهات الإستخدام على الخادم ومن ثم إخراجها Render للعميل. يمكنك الإطلاع على التوثيق الخاص بمحرك القوالب Pug من الموقع الرسمي الخاص به.

أنشئ مجلدًا جديدًا باسم views داخل المجلد الرئيسي للمشروع، وأنشئ ملفًا جديدًا باسم page.pug، ضع بداخله الشيفرة البرمجية التالية:

doctype html
block head
    meta(charset='UTF-8')
    meta(http-equiv='X-UA-Compatible' content='IE=edge')
    meta(name='viewport' content='width=device-width, initial-scale=1.0')
    link(href='https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css' rel='stylesheet')
body
.w-full.max-w-8xl.min-w-0.mx-auto.px-6
    .flex.mt-12.bg-white.rounded-md.shadow
        .w-64.bg-blue-100.rounded-l-md.border-r.border-dashed.border-blue-200
            .flex.justify-center.items-center.h-32.text-blue-700.text-center.font-semibold.text-3xl.italic
                | Loyalty Program
            .mt-8.border-t.border-blue-200
                a.block.py-3.px-6.text-blue-700.font-semibold.border-b.border-blue-200(href='/')
                    | Home
                a.block.py-3.px-6.text-blue-700.font-semibold.border-b.border-blue-200(href='/carts')
                    | Abandoned Carts
                a.block.py-3.px-6.text-blue-700.font-semibold.border-b.border-blue-200(href='/coupon')
                    | Coupons
                a.block.py-3.px-6.text-blue-700.font-semibold.border-b.border-blue-200(href='/loyalty')
                    | Loyalty


        block content
            .flex-grow
            .flex.flex-col.mx-2.mt-1
                .px-3.py-4.flex.justify-center

لن نشرح صيغة كتابة قوالب pug لأنها بسيطة جدًا، وتحفيزك أيضًا للإطلاع على التوثيق الرسمي الخاص بـ Pug.

اقتباس

ملاحظة: في هذا المشروع سنستخدم الإطار TailwindCSS ليكون المسؤول عن تنسيق صفحات المشروع CSS. لمن لا يعرف إطار TailwindCSS هو إطار عمل خاص بلغة CSS مشابه لإطار Bootstrap ولكنه يتميز بصغر حجمه موازنةً ببقية أطر العمل. سبب استخدام Tailwind كان فقط من أجل عدم الدخول في تفاصيل تنسيق الصفحات CSS ويمكنك استعمال أي إطار شئت أو الاعتماد على تنسيق CSS مباشرةً.

يتيح محرك القوالب Pug إنشاء كتل برمجية blocks وإعادة استخدامها لاحقا في أي مكان تحتاجه، كمثال على ذلك، هو ما فعلناه في الملف السابق page.pug، بحيث أنشأنا كتلة برمجية سنستخدمها في بقية الصفحات.

في الشيفرة البرمجية السابقة التي تخص الملف page.pug، ﻻحظ أننا أنشأنا كتلة block باسم head وأضفنا إليها ما يتعلق بالبيانات الوصفية Metadata، مع إرفاق ملف خاص بإطار TailwindCSS، باستخدام شبكات الوصول CDN.

بعدها أضفنا جسم الصفحة تحت ترويسة body، تحتوي على قائمة جانبية. وتحتها أضفنا كتلة جديدة تحت اسم content وهي ما سيحمل مختلف محتوى الصفحات الخاصة بالتطبيق.

سنريكم صورةً عن الناتج النهائي المتوقع الحصول عليه:

First_Page.png

ننتقل اﻵن ﻹنشاء صفحة لعرض قائمة سلات الشراء المتروكة، باستخدام نقطة الوصول التي أنشأناها سابقًا، بحيث سنسترجع مصفوفة الكائنات التي تحصلنا عليها من الواجهة البرمجية والتي تخص متجر العميل، ونستخرج المعلومات التي نحتاجها باستخدام حلقة تكرارية بسيطة.

أنشئ ملفًا جديدًا داخل المجلد views باسم abandoned.pug وضع بداخله الشيفرة التالية:

extends page.pug 
block append head
    title Abandoned Carts
block content 
  table.w-full.text-md.bg-white.shadow-md.rounded.mb-6.max-w-6xl.min-w-0.mx-auto.px-6
    thead
      tr.border-b
        th.text-left.p-1.px-4 Customer Name
        th.text-left.p-1.px-4 Customer Email
        th.text-left.p-1.px-4 Customer Mobile
        th.text-left.p-1.px-4 Products In Cart
        th.text-left.p-1.px-4 Cart Total(SAR)
        th.text-left.p-1.px-4 Phase
    tbody 
    each element in renderedData 
      tr.border-b.bg-gray-100(class='hover:bg-orange-100')
        td.p-3.px-5=element.customer_name
        td.p-3.px-5=element.customer_email
        td.p-3.px-5=element.customer_mobile
        td.p-3.px-5=element.products_count
        td.p-3.px-5=element.cart_total
        td.p-3.px-5=element.phase

سأشرح هذا الكود باستفاضة.

في السطر اﻷول، عملنًا تمديدًا للقالب اﻷب الذي أنشأناه سابقا، بحيث أن القالب الجديد (القالب ابن) سيأخذ كل محتوى القالب السابق ويضيف عليه ما نريد:

extends page.pug

أما في السطرين التاليين، طلبنا من محرك القوالب Pug أن يعدل الكتلة block المسمى head في القالب الأب بما تحت هذا السطر، وهنا أعدنا تسمية عنوان الصفحة بـ Abandoned Carts:

block append head
    title Abandoned Carts

بعدها أنشأنا جدولًا Table وقسمناه إلى 6 أعمدة تحمل عناوين المعلومات التي نريد عرضها.

    each element in renderedData 

في هذا السطر، أنشأنا حلقة تكرارية تستخرج المعلومات المطلوبة من مصفوفة الكائنات Array of Objects التي تحصلنا عليها سابقا، إذ وضعنا المعلومات المطلوبة في سطور حسب نوعيتها.

بقيت لنا خطوة واحدة الآن، وهي ربط الصفحة التي أنشأناها مع نقطة الوصول التي سبق و اضفناها. توجه إلى الملف index.js وعدِّل نقطة الوصول السابقة لتكون كالتالي:

app.get("/carts", async (req, res) => {
  const response = await AbandonedCarts();
  const renderedData = response["abandoned-carts"];
  res.render("abandoned", { renderedData });
});

لاحظ أننا غيرنا السطر الأخير واستبدلنا التابع send بتابع جديد هو render، والذي يأخذ معاملين، المعامل الأول هو القالب، والمعامل الثاني هي البيانات التي ستُعرَض في القالب.

يجب التأكد من أن اسم القالب المرسل كمعامل للتابع render هو بنفس الاسم الذي يحمله القالب المعني. يجب التأكد أيضًا أن عنوان المعامل الثاني والمرسل كبيانات للعرض، هي بنفس الأسم الموجود في القالب الذي أُنشئ لهذا الشأن.

شغِّل الآن الخادم الخاص بنا عن طريق تنفيذ الأمر:

yarn start

لم يعمل معك! بل وظهرت الكثير من رسائل الخطأ، صحيح؟

عد إلى ملف index.js وأضف الأسطر التالية مباشرة قبل أول نقطة وصول Endpoint أنشأناها:

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");
app.use(express.static(path.join(__dirname, "public")));

ما الذي تعنيه تلك الأسطر؟ ببساطة، طلبنا من إطار ExpressJS أن يستخدم Pug كنظام قوالب، وأيضًا أن يتعرف على مسار ملفات القوالب التي أنشأناها والموجودة داخل المجلد views.

أما بالنسبة للسطر الأخير، فهي أننا أخبرنا ExpressJS بأن المجلد النهائي للقوالب بعد أن تحدث عملية التصدير/الاستخراج rendering أن يضع الملفات الناتجة في المجلد public.

قبل أن تعيد تشغيل الخادم، استورد المكتبة path في بداية الملف، تمامًا بعد استيراد express مع إنشاء ثابت جديد باسم dirname__ والذي سيحوي التابع resolve الخاص بالمكتبة path وبالتالي سيكون الملف index.js كالتالي:

import express from "express";
import path from "path";
import AbandonedCarts from "./zid.js";

const __dirname = path.resolve();
var app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");
app.use(express.static(path.join(__dirname, "public")));

app.get("/carts", async (req, res) => {
  const response = await AbandonedCarts();
  const renderedData = response["abandoned-carts"];
  res.render("abandoned", { renderedData });
});

const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`Server started on ${port}`);
});

الآن، شغل الخادم الخاص بالتطبيق، وافتح المتصفح وادخل العنوان التالي:

http://localhost:3000/carts

ستظهر لنا النافذة التالي (اُخفيت معلومات العملاء حفاظا على الخصوصية):

Aband_Carts_First_Screen.png

تهانينا، لقد أكملت أول صفحات تطبيق.

إنشاء قسائم التخفيض Coupons

ننتقل الآن إلى بناء صفحة إنشاء قسيمة تخفيض باستخدام الواجهة البرمجية لمنصة زد، يرجى مراجعة التوثيق الخاص بها من أجل معرفة المعلومات المطلوبة.

أنشئ ملفًا جديدًا داخل المجلد views باسم coupon.pug وضع الشيفرة البرمجية التالية:

extends page.pug 
block append head
    title Coupon
block content 
form#form(action='/add-coupon' method='POST').place-items-center.max-w-max.m-4.p-2.bg-white.rounded.shadow-xl
    p.text-gray-800.font-medium Adding new coupon code
    div
      label.block.text-sm.text-gray-00(for='name') Choose a name for the coupon
      input.w-full.px-5.py-1.text-gray-700.bg-gray-200.rounded(name='name' type='text' required='' placeholder='Coupon Name')
    .mt-2
      label.block.text-sm.text-gray-600(for='code') Coupon Code
      input.w-full.px-5.py-4.text-gray-700.bg-gray-200.rounded(name='code' type='text' required='' placeholder='Hsoub10')
    .mt-2
      label.block.text-sm.text-gray-600(for='discount_type') Discount Type
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='discount_type' type='text' required='' placeholder='p for percentage and f for fixed')
    .mt-2
      label.text-sm.block.text-gray-600(for='discount') Discount
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='discount' type='text' required='' placeholder='discount')
    .inline-block.mt-2.pr-1(class='w-1/2')
      label.block.text-sm.text-gray-600(for='free_shipping') Free Shipping
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='free_shipping' type='text' required='' placeholder='1 to enable, 0 to disable')
    .inline-block.mt-2.-mx-1.pl-1(class='w-1/2')
      label.block.text-sm.text-gray-600(for='free_cod') Free Cash on Delivery
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='free_cod' type='text' required='' placeholder='1 to enable, 0 to disable')
    div
      label.block.text-sm.text-gray-600(for='total') Total to apply coupon
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='total' type='text' required='' placeholder='Total to apply coupon')
    .inline-block.mt-2.pr-1(class='w-1/2')
      label.block.text-sm.text-gray-600(for='date_start') Starting Date
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='date_start' type='text' required='' placeholder='2021-06-01')
    .inline-block.mt-2.-mx-1.pl-1(class='w-1/2')
      label.block.text-sm.text-gray-600(for='date_end') Ending Date
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='date_end' type='text' required='' placeholder='2021-12-31')
    .mt-2
      label.block.text-sm.text-gray-600(for='uses_total') Total Using
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='uses_total' type='text' required='' placeholder='1000')
    .mt-2
      label.block.text-sm.text-gray-600(for='uses_customer') Total Using Per Customer
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='uses_customer' type='text' required='' placeholder='5')
    .mt-2
      label.block.text-sm.text-gray-600(for='apply_to') Apply for all Products?
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='apply_to' type='text' required='' placeholder='all or apply_to_array[]')
    .mt-2
      label.block.text-sm.text-gray-600(for='status') Active?
      input.w-full.px-2.py-2.text-gray-700.bg-gray-200.rounded(name='status' type='text' required='' placeholder='1 to enable, 0 to disable')

    .mt-4
      button.px-4.py-1.text-white.font-light.tracking-wider.bg-gray-900.rounded(type='submit') Add

في بداية الملف، استوردنا الشيفرة الموجودة في الملف page.pug والتي تحوي القائمة الجانبية واستيراد إطار TailwindCSS.

بعدها أنشأنا نموذج Form لإرسال البيانات إلى نقطة الوصول التي سنبنيها الآن.

أنشئ ملفًا جديدًا باسم addCoupon.js ونفّذ ما فعلناه في الملف zid.js ألا وهو استيراد مكتبة Axios من أجل إرسال طلبات إلى الواجهة البرمجية لمنصة زد ولكن هذه المرة بطريقة POST، وهذا حسب التوثيق الخاص بالمنصة:

import axios from "axios";
const ZidAPI = "https://api.zid.dev/app/v1";

الآن سنحتاج إلى إنشاء دالة function ونسند إليها مهمة إرسال الطلب إلى الواجهة البرمجية لمنصة زد بالشكل التالي:

const AddCoupon = async (couponInfo) => {
  const headers = {
    "X-MANAGER-TOKEN": process.env.MANAGER_TOKEN,
    Authorization: "Bearer " + process.env.auth,
  };

أنشأنا الدالة تحت اسم addCoupon والتي تأخذ معاملًا واحدًا ويجب أن يكون كائنًا. حددنا ترويسة الطلب الذي سنرسله داخل الثابت headers.

الخطوة الثانية هي أن ننشئ ثابتًا constant يحوي جميع المعلومات المطلوبة من طرف الواجهة البرمجية لمنصة زد، الثابت يكون كائنا من أجل أن يحمل جميع المعلومات.

  const data = {
    name: couponInfo.name,
    code: couponInfo.code,
    discount_type: couponInfo.discount_type,
    discount: couponInfo.discount,
    free_shipping: couponInfo.free_shipping,
    free_cod: couponInfo.free_cod,
    total: couponInfo.total,
    date_start: couponInfo.date_start,
    date_end: couponInfo.date_end,
    uses_total: couponInfo.uses_total,
    uses_customer: couponInfo.uses_customer,
    apply_to: couponInfo.apply_to,
    status: couponInfo.status,
  };

الآن ننشئ كتلة Try - Catch لإرسال الطلب للواجهة البرمجية بحيث أنشأنا ثابتًا باسم addCoupon يحوي الطلب المرسل إلى الواجهة البرمجية عن طريق مكتبة Axios كالتالي:

  try {
    const addCoupon = await axios.post(
      `${ZidAPI}/managers/store/coupons/add`,
      data,
      {
        headers: headers,
      }
    );
    if (addCoupon) {
      return addCoupon.data;
    }
  } catch (error) {
    return error;
  }

تحققنا من عدم وجود أي خطأ وفي حالة وجود إجابة من الخادم نرجع محتوى الإجابة عن طريق السطر:

return addCoupon.data;

أما في حالة وجود خطأ، فسيكون الانتقال مباشرةً إلى الجزء catch وهنا سيُسترجع محتوى الخطأ عن طريق السطر:

 return error;

في أخر الملف، نصدِّر الدالة حتى نستطيع استخدامها في أماكن أخرى باستخدام السطر:

};

export default AddCoupon;

اﻵن نضيف نقطة وصول جديدة Endpoint إلى الملف index.js بنفس الطريقة التي أضفنا بها نقاط الوصول السابقة.

استدع في أعلى الملف الملف addCoupon كالتالي:

import AddCoupon from ‘./addCoupon.js

لتكون بداية الملف تمامًا كالتالي:

import express from "express";
import dotenv from "dotenv";
import path from "path";
import AbandonedCarts from "./zid.js";
import AddCoupon from "./addCoupon.js";

بعدها سنضيف نقطة الوصول بالشكل التالي:

app.post("/add-coupon", async (req, res) => {
  const data = req.body;
  const dataToSend = Object.assign({}, data);
  const response = await AddCoupon(dataToSend);
  if (response) {
    res.redirect("/");
  } else {
    res.redirect("/error");
  }
});

أعتقد أنك قد ﻻحظت أن نقطة الوصول هذه مختلفة بعض الشيء عن سابقاتها، ﻻ تقلق، سأشرح كل شيء بالتفصيل.

في السطر اﻷول، أنشأنا نقطة الوصول الجديدة والتي تستعمل POST ﻹرسال البيانات، الرابط سيكون:

/add-coupon

ﻻحظت أننا أضفنا كلمة مفتاحية جديدة هي async وهي ببساطة تعني أن هنالك عملية ستأخذ وقتا غير معروف لهذا قبل الانتهاء من جميع العمليات المطلوبة، يجب إنهاء العمليات التي تأخذ وقتا، من الأفضل أن تتعرف أكثر على تقنية async/await خصوصا في NodeJS.

في السطر الثاني، أنشأنا ثابتا جديدا باسم data والذي يحوي جسم الطلب المرسل Request إلى نقطة الوصول، في هذه النقطة ﻻ يزال جسم الطلب خاما، ويجب أن يُعدَّل ليكون كائنًا Object؛ أما في السطر الثالث، فقد عدّلنا أو تنقية الثابت data باستخدام التابع assign المدمج افتراضيًا في لغة Javascript، أُسنِدت النتيجة إلى الثابت dataToSend. بينما في السطر الرابع، أنشأنا ثابتًا جديدًا باسم response وأسندنا نتيجة الدالة AddCoupon إليه.

لاحظ أننا استخدمنا كلمة مفتاحية جديدة هي await قبل الدالة AddCoupon، وهنا طلبنا أن ينتظر NodeJS العملية حتى انتهائها قبل القيام بتنفيذ السطر الموالي.

كخطوة أخيرة، وضعنا شرطًا للتحقق إن كنا قد تحصلنا على إجابة من الخادم Response أم ﻻ. في حالة الحصول على إجابة، سيُنفَّذ السطر:

res.redirect("/");

وهنا، سيُعاد توجيه المستخدم إلى الصفحة الرئيسية. أما في حالة عدم الحصول على إجابة، فسيُنفَّذ السطر:

res.redirect("/error");

ويعني أنه سيُعاد توجيه المستخدم إلى صفحة الخطأ وهي في هذه الحالة نقطة وصول ستُنشأ لاحقًا لعرض رسائل الخطأ.

اﻵن، بقيت لنا خطوة أخيرة، وهي إنشاء صفحة يستطيع المتصفح الوصول إليها. ببساطة، ننشئ نقطة وصول جديدة باسم coupon والتي تكون من نوع GET، تعرض فقط القالب coupon الذي سبق وأنشأناه.

نقطة الوصول ستكون كالتالي:

app.get("/coupon", async (req, res) => {
  res.render("coupon");
});

شغل خادم التطبيق باستخدام الأمر:

yarn start

توجه إلى الرابط التالي:

http://localhost:3000/coupon

إن ظهرت لديك رسالة خطأ مشابهة لهذه الرسالة:

Error:/zid-local/views/coupon.pug:5:1

Only named blocks and mixins can appear at the top level of an extending template
at makeError (/zid-local/node_modules/pug-error/index.js:34:13)

تأكد من إزاحة indentation السطور في الصفحة، ويفضل استعمال إضافة pug (jade) formatter والتي تساعدك في تنسيق الشفرات البرمجية الخاصة بمحرك القوالب Pug.

بعد تثبيت اﻹضافة، اضغط على Alt+Shift+F لتنسيق الشيفرة البرمجية وحل المشكلة.

سيظهر نموذج إنشاء قسيمة التخفيض التي أنشأناها سابقًا باستخدام الملف coupon.pug:

Add_Coupon.png

أدخل المعلومات الخاصة بقسيمة التخفيض واضغط على إضافة Add من أجل إرسال المعلومات إلى نقطة الوصول التي اضفناها سابقًا add-Coupon ومنها تُعالَج البيانات وتُرسَل إلى الواجهة البرمجية لمنصة زد.

في حالة إنشاء قسيمة التخفيض، فسيوجَّه المستخدِم إلى الصفحة الرئيسية التي نحن بصدد إنشائها، أما في حالة وجود خطأ ما، أو عدم القدرة على إرسال الطلب بطريقة صحيحة، سيُعاد توجيه المستخدم إلى صفحة الخطأ التي أنشأناها سابقًا.

إنشاء صفحة الأخطاء

اﻵن، أنشئ ملفًا جديدًا داخل المجلد views باسم error.pug وضع بداخله الشيفرة البرمجية التالية:

doctype html
block head
    meta(charset='UTF-8')
    meta(http-equiv='X-UA-Compatible' content='IE=edge')
    meta(name='viewport' content='width=device-width, initial-scale=1.0')
    link(href='https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css' rel='stylesheet')
body
block content 
    style.
        .gradient {
        background-image: linear-gradient(135deg, #684ca0 35%, #1c4ca0 100%);
        }
    .gradient.text-white.min-h-screen.flex.items-center
        .container.mx-auto.p-4.flex.flex-wrap.items-center

        .w-full.text-center.p-4(class='md:w-7/12 md:text-left')
            .text-6xl.font-medium 404
            .text-xl.font-medium.mb-4(class='md:text-3xl')
                | Oops. This page has gone missing.
            .text-lg.mb-8
                | You may have mistyped the address or the page may have moved.
            a.border.border-white.rounded.p-4(href='/') Go Home

في الملف index.js، أنشئ نقطة وصول جديدة كالتالي:

app.get("/error", (req, res) => {
  res.render("error");
});

اﻷمر بسيط ونقطة الوصول هذه تعرض القالب error.pug الذي أنشأناه قبل قليل، حول أي أخطاء تظهر معك إلى الرابط ‎/error من أجل عرض هذه الصفحة.

إنشاء الصفحة الرئيسية للتطبيق

لننشئ الصفحة الرئيسية للتطبيق، أنشئ ملفًا جديدًا داخل المجلد views باسم index.pug، ثم ضع الشيفرة التالية بداخله:

extends page.pug 
block append head
  title Welcome
block content
  .flex.mx-auto.items-center.justify-center.shadow-lg.mt-65.mx-8.mb-4.max-w-lg
    form.w-full.max-w-xl.bg-white.rounded-lg.px-4.pt-2
      .flex.flex-wrap.-mx-3.mb-6
        h2.px-4.pt-3.pb-2.text-gray-800.text-lg أضف مفتاح المتجر الخاص بك
        .w-full.px-3.mb-2.mt-2(class='md:w-full')
          textarea.bg-gray-100.rounded.border.border-gray-400.leading-normal.resize-none.w-full.h-20.py-2.px-3.font-medium.placeholder-gray-700(class='focus:outline-none focus:bg-white' name='body' placeholder='Paste your store X-MANAGER-TOKEN HERE' required='')
        .w-full.flex.items-start.px-3(class='md:w-full md:w-full')
          .-mr-1
            input.bg-white.text-gray-700.font-medium.py-1.px-4.border.border-gray-400.rounded-lg.tracking-wide.mr-1(type='submit' class='hover:bg-gray-100' value='Add')

الآن، علينا أن ننشئ نقطة الوصول الخاصة بالصفحة الرئيسية، في ملف index.js ننشئ نقطة الوصول كما تعودنا، تستخدم طريقة GET لاستقبال البيانات من الخادم:

app.get("/", async (req, res) => {
  res.render("index");
});

نقطة الوصول هذه بسيطة، بحيث أن رابطها هو الرابط الرئيسي للخادم، وتعرض فقط القالب الخاص بالصفحة الرئيسية، والنتيجة ستكون كالتالي:

Home.png

أضفنا حقلًا لإدخال مفتاح مفتاح الوصول الخاص بالمتجر X-MANAGER-TOKEN ليُستخدم مستقبلًا، وسنشرح سبب إضافة هذا الحقل -الواضح- عند الانتهاء من بناء المنصة.

بناء خدمة إرسال رسائل بريد إلكتروني

بعد أن استرجعنا واستعراض سلات الشراء المتروكة، وأنشأنا قسيمة تخفيض، علينا الآن إرسال بريد إلكتروني يحمل قسيمة التخفيض التي أنشأناها لأصحاب سلات الشراء المتروكة، من أجل تحفيزهم على إنهاء طلباتهم وبالتالي زيادة مبيعات المتجر الخاص بنا.

لإرسال رسائل البريد الإلكتروني بطريقة بسيطة، سنستخدم SendGrid والذي يوفر خدمة إرسال مجاني لرسائل البريد الإلكتروني جيدًا للاستخدام البسيط (أو أي خدمة أخرى تختارها).

أنشئ حسابًا على منصة SendGrid وفعل حسابك. بعدها، افتح نافذة سطر أوامر في نفس مجلد التطبيق ونفذ الأمر التالي:

yarn add @sendgrid/mail

سيثبّت هذا الأمر المكتبة الخاصة بمنصة SendGrid.

أنشئ ملفا جديدا باسم mail.js وأضف الشيفرة البرمجية التالية:

import sgMail from "@sendgrid/mail";

sgMail.setApiKey(process.env.SENDGRID_API_KEY);

const sendMail = async (mailData) => {
  const msg = {
    to: mailData.to,
    // SendGrid أضف البريد اﻹلكتروني الذي سجلت به في منصة 
    from: "info@email.com", 
    subject: mailData.subject,
    text: mailData.text,
    html: mailData.html,
  };
  const sendingMail = await sgMail.send(msg);
  if (sendingMail) {
    return "Mail has been sent";
  } else {
    console.log("SENDING MAIL ERROR ===> ", sendingMail);
    return "There was a problem in sending the mail";
  }
};

export default sendMail;

بداية، استوردنا مكتبة منصة SendGrid التي ثبتناها قبل قليل، بعدها أضفنا المفتاح المساعد الخاص بحسابنا على المنصة، والذي تحصل عليه من لوحة التحكم الخاصة بحسابك.

أنشأنا دالة باسم sendMail والتي تأخذ معاملًا واحدًا يجب أن يكون كائنًا Object والذي سيحوي المعلومات المطلوبة لإرسال البريد الإلكتروني.

أنشأنا ثابتًا جديدًا باسم msg والذي يحوي كل من الحقول التالي:

  • Subject: يمثل عنوان البريد الإلكتروني الذي نريد إرساله
  • From: وهو عنوان البريد الإلكتروني الذي سنرسل منه الرسائل، وهو نفس عنوان البريد الإلكتروني الذي أضفتَه إلى حسابك على منصة SendGrid
  • `Subject: عنوان رسالة البريد الإلكتروني التي سنرسلها.
  • Text: نص رسالة البريد الإلكتروني التي سترسله، يجب أن يكون نصا فقط
  • Html: نفس نص رسالة البريد الإلكتروني، ولكن بإمكانك أن تضيف تنسيقات HTML.

أنشأنا ثابتًا باسم sendingMail يحوي القيمة الراجعة من التابع send الخاص بمكتبة SendGrid والتي سترسل رسالة البريد اﻹلكتروني إن كانت جميع المدخلات صحيحة.

أنشأنا شرطًا بعدها يتحقق إن كانت هنالك إجابة من الواجهة البرمجية لمنصة SendGrid، إن كانت هنالك إجابة ستُرجَع رسالة تفيد بأنه قد تمت عملية اﻹرسال بنجاح. في حالة عدم وجود إجابة من الواجهة البرمجية أو أرجعَت خطأ، نعرض الخطأ عن السطر الموجود بداخل الجزء الثاني من كتلة الشرط، وبعدها نرجع رسالة تفيد بأن هنالك مشكلة عند إرسال البريد اﻹلكتروني:

console.log("SENDING MAIL ERROR ===> ", sendingMail);
return "There was a problem in sending the mail";

في النهاية، صدّرنا الدالة من أجل نستخدمها في مختلف أجزاء التطبيق.

اﻵن سنضيف نقطة وصول جديدة لمشروعنا، في الملف index.js أضف الشيفرة البرمجية التالية:

app.post("/mail", async (req, res) => {
  const data = req.body;
  const dataToSend = Object.assign({}, data);
  const send = await sendMail(dataToSend);
  if (send) {
    res.send("mail has been sent");
  } else {
    res.send("there was an error");
  }
});

متأكد أنك قد تعودت على صيغة إضافة نقاط الوصول إلى المشروع، نقطة الوصول هذه مشابهة جدا لنقطة الوصول السابقة التي أضفناها سابقًا.

تركيب أجزاء التطبيق مع بعضها

انتهينا إلى الآن من بناء الخصائص اﻷساسية للتطبيق، بقي لنا أن نجمع هذه الخصائص كلها من أجل الوصول إلى هدفنا، وهو إرسال بريد إلكتروني لجميع أصحاب سلات الشراء المتروكة حتى نحفزهم على إكمال عملية الشراء بإرسال قسيمة تخفيض إليهم عن طريق البريد اﻹلكتروني.

أنشئ ملفًا جديدًا داخل المجلد views باسم loyalty.pug وضع بداخله الشيفرة البرمجية التالية:

extends page.pug 
block append head
  title Loyalty
block content 
  .wrapper.px-2.w-full
    p.text3xl.items-center.justify-center Send a coupon code to all abandoned carts
    form.max-w-sm.bg-gray-100.px-3.py-5.rounded.shadow-lg.my-10.m-auto(action='/loyalty' method='POST')
      .flex.flex-col.space-y-3
        .flex.items-center.bg-white.border.border-gray-100.rounded.px-2
          input.w-full.py-2.px-1.placeholder-indigo-400.outline-none.placeholder-opacity-50(type='text' placeholder='Enter Coupon Code' autocomplete='off')
        button.text-white.bg-indigo-500.px-4.py-2.rounded(type='submit')
          | Send Coupon

الصفحة بسيطة، فيها نافذة فقط ﻹضافة قسيمة التخفيض

Loyalty.png

أنشئ ملفًا جديدًا باسم loyalty.js يحمل الشفرة البرمجية التالية:

import AbandonedCarts from "./zid.js";
import sendMail from "./mail.js";

const Loyalty = async (couponCode) => {
  const getAbandondCarts = await AbandonedCarts();
  if (getAbandondCarts) {
    const response = getAbandondCarts["abandoned-carts"];
    if (response) {
      const getEmails = [];
      for (let i = 0; i < response.length; i++) {
        getEmails.push(response[i].customer_email);
      }

      const mailData = {
        to: getEmails,
        // استخدم عنوان البريد اﻹلكتروني الذي سجلت به
        from: "info@geekcademy.com", 
        subject: "Special Offer for you",
        text: "يرجى استخدام قسيمة التخفيض التالية " + couponCode + " من أجل إنهاء طلبك",
        html: "يرجى استخدام قسيمة التخفيض التالية " + couponCode + " من أجل إنهاء طلبك",
      };
      return await sendMail(mailData);
    } else {
      return "There was an error getting abandoned carts";
    }
  } else {
    return "There is a server error";
  }
};

export default Loyalty;

بداية استوردنا كل من دالة جلب سلات الشراء المتروكة AbandonedCarts ودالة إرسال رسائل البريد اﻹلكتروني.

بعدها أنشأنا دالةً جديدةً تحت اسم Loyalty والتي ستدمج الدالتين السابقتين بحيث ستجلب سلات الشراء المتروكة أوﻻ، ومن ثم التحقق من وجود المعلومات المطلوبة:

const getEmails = [];

هنا أنشأنا ثابتًا جديدًا يمثل مصفوفة فارغة، ستوضع عناوين البريد المستخرجة من المعلومات التي حُصِّلت عليها من سلات الشراء المتروكة لعملاء المتجر، بعدها أنشأنا حلقة تكرارية، بحيث تمسح جميع المعلومات المتحصل عليها من الثابت response بعد التحقق من أنه يحوي المعلومات المطلوبة وهي مصفوفة كائنات.

مع كل دورة للحلقة التكرارية، يُستخرَج البريد الإلكتروني للعميل الذي ترك سلته دون شراء وإضافته إلى الثابت getEmails ثم أنشأنا بعدها ثابتًا جديدًا تحت اسم mailData والذي سيحوي المعلومات المطلوبة في الدالة sendMail من أجل إرسال رسائل البريد اﻹلكتروني، بعدها أسندت قيمة الثابت mailData إلى الدالة sendMail من أجل إرسال رسالة إلى جميع أصحاب سلات الشراء المتروكة على عناوين بريدهم الإلكتروني المسجل.

علينا اﻵن أن نضيف هذا الخاصية إلى ملف index.js، مثل أي نقطة نهاية أضفناها سابقا.

نقطة الوصول اﻷولى، خاصة بعرض الصفحة التي أنشأناها للتو:

app.get("/loyalty", async (req, res) => {
  res.render("loyalty");
});

تعرض نقطة الوصول هذه القالب الموجود داخل المجلد views المسمى باسم loyalty.pug.

نقطة الوصول الثانية، وهي التي ستحوي على الدالة التي أضفناها قبل قليل بالشكل التالي:

app.post("/loyalty", async (req, res) => {
  const response = await Loyalty();
  res.send(response);
});

يمكنك اﻵن التوجه إلى الرابط وجرب التطبيق:

http://localhost:3000/loyalty

اﻵن يمكنك إدخال قسيمة التخفيض في المكان المخصص وإرسالها إلى جميع أصحاب سلات الشراء المتروكة.

عند اﻹنتهاء من جميع الخطوات التي ذُكرت في هذا المقال، ستكون الشيفرة البرمجية في الملف index.js كالتالي:

import express from "express";
import path from "path";
import AbandonedCarts from "./zid.js";
import AddCoupon from "./addCoupon.js";
import sendMail from "./mail.js";
import Loyalty from "./loyality.js";

const __dirname = path.resolve();
var app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");
app.use(express.static(path.join(__dirname, "public")));

app.get("/carts", async (req, res) => {
  const response = await AbandonedCarts();
  const renderedData = response["abandoned-carts"];
  res.render("abandoned", { renderedData });
});

app.get("/", async (req, res) => {
  res.render("index");
});

app.get("/loyalty", async (req, res) => {
  res.render("loyalty");
});

app.get("/coupon", async (req, res) => {
  res.render("coupon");
});

app.get("/error", (req, res) => {
  res.render("error");
});

app.post("/loyalty", async (req, res) => {
  const response = await Loyalty();
  res.send(response);
});

app.post("/add-coupon", async (req, res) => {
  const data = req.body;
  const dataToSend = Object.assign({}, data);
  const response = await AddCoupon(dataToSend);
  if (response) {
    res.redirect("/");
  } else {
    res.redirect("/error");
  }
});

app.post("/mail", async (req, res) => {
  const data = req.body;
  const dataToSend = Object.assign({}, data);
  const send = await sendMail(dataToSend);
  if (send) {
    res.send("mail has been sent");
  } else {
    res.send("there was an error");
  }
});

const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`Server started on ${port}`);
});

أخيرًا، تجد شيفرة التطبيق كاملةً على GitHub بالمستودع zid-retention-app.

خاتمة

لقد استغللنا في البداية الواجهة البرمجية لمنصة زد وما تقدمه من نقاط وصول وبناء فكرة تطبيق منها يمكن أن يستفيد منه أصحاب المتاجر الذين يعتمدون على منصة زد، إذ يمكنك توفير التطبيق لهم بمقابل لكي يستفيدوا هم أيضًا منه وبذلك يستفيد الجميع. لا يقتصر الأمر على فكرة السلات المتروكة وزيادة الولاء وإنما يمكن بناء عشرات التطبيقات المفيدة لأصحاب المتاجر التي توسع من عمل متاجرهم وكان هذا مجرد مثال، وهو في الحقيقة ما يمكنك عمله مع معظم منصات التجارة اﻹلكترونية مثل منصة سلة وشوبيفاي Shopify، يكفي فقط أن تقرأ التوثيق الخاص بالواجهات البرمجية لتلك المنصات وبناء أفكار مفيدة منها.

لكي يكون التطبيق تجاريًا، عليك إضافة نظام لتسجيل الاعضاء ونظام اشتراكات وأيضا نظام تسجيل الدخول والخروج مع إمكانية إضافة المعلومات الشخصية لصاحب المتجر الراغب في التسجيل، وهنا سنستخدم الصفحة الرئيسية التي أضفناها إلى التطبيق index.pug، بحيث سيكون بإمكان العميل المسجل إضافة مفتاح المتجر الخاص به سواءً على منصة زد، أو إضافة المعلومات المطلوبة للربط مع التطبيق في حالة استخدامه لمنصة أخرى مثل Shopify.

يمكنك أيضًا إضافة خصائص جديدة لتطبيق كما أشرنا مثل إرسال رسالة نصية للعملاء أصحاب سلات الشراء المتروكة بالتزامن مع إرسال بريد إلكتروني. يمكنك استخدام منصات إرسال الرسائل النصية المتوفرة في بلدك إن وجدت أو استخدام حلول مثل Twilio أو MessageBird. وطبعًا يمكنك توسيع الخدمات التي تقدمها إلى خدمات أخرى يطلبها أصحاب المتاجر كما أشرنا وهنا نترك الأمر لك ولإبداعك.

تهانينا، لقد أتممت بناء تطبيق مفيد بإمكانك استعماله بطريقة تجارية إن أحببت وﻹطلاق شركتك الناشئة العاملة في هذا المجال، ولم ﻻ توسعها لتشمل منصات أكثر غير منصة زد فقط! نرجو لك كل التوفيق في رحلتك القادمة.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...