بعد أن غطينا معظم ما يتعلق بالواجهات البرمجية وكيفية عملها، مرورًا بالتعرف على الواجهة البرمجية لمنصة زد، سنعمل على بناء تطبيق يتصل بالواجهة البرمجية لمنصة زد ويوسع من عمل المتاجر المبنية بتلك المنصة بإضافة ميزة لها وهي زيادة المبيعات بزيادة الاحتفاظ العملاء وكسب ولائهم.
هذا المقال هو جزء من سلسلة مقالات حول الواجهة البرمجية API وكيفية الاستفادة منها في بناء تطبيق ويب:
- مدخل إلى الواجهات البرمجية API
- الاتصال بواجهة زد البرمجية وفهم عملية الاستيثاق والتصريح
- أمثلة عملية لاستخدام واجهة برمجة متاجر زد zid API
- تطوير تطبيق عملي يزيد من احتفاظ العملاء عبر واجهة زد البرمجية
فكرة التطبيق
فكرة التطبيق سهلة وبسيطة، بحيث سنستخدم نقطة الوصول الخاصة بسلات الشراء المتروكة لاستعراض العملاء الذين تركوا سلة ما دون إتمام عملية الشراء لمتجر ما ومن ثم سنولد عبر نقطة وصول القسائم قسيمة تخفيض 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
يفترض أن تحصل على الرسالة التالية:
تهانينا، أتممت تهئية التطبيق وإنشاء أولى نقاط الوصول 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 ووضع المفتاح المناسب الخاص بك مكانها.
في بداية الملف 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 وهي ما سيحمل مختلف محتوى الصفحات الخاصة بالتطبيق.
سنريكم صورةً عن الناتج النهائي المتوقع الحصول عليه:
ننتقل اﻵن ﻹنشاء صفحة لعرض قائمة سلات الشراء المتروكة، باستخدام نقطة الوصول التي أنشأناها سابقًا، بحيث سنسترجع مصفوفة الكائنات التي تحصلنا عليها من الواجهة البرمجية والتي تخص متجر العميل، ونستخرج المعلومات التي نحتاجها باستخدام حلقة تكرارية بسيطة.
أنشئ ملفًا جديدًا داخل المجلد 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
ستظهر لنا النافذة التالي (اُخفيت معلومات العملاء حفاظا على الخصوصية):
تهانينا، لقد أكملت أول صفحات تطبيق.
إنشاء قسائم التخفيض 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 من أجل إرسال المعلومات إلى نقطة الوصول التي اضفناها سابقًا 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"); });
نقطة الوصول هذه بسيطة، بحيث أن رابطها هو الرابط الرئيسي للخادم، وتعرض فقط القالب الخاص بالصفحة الرئيسية، والنتيجة ستكون كالتالي:
أضفنا حقلًا لإدخال مفتاح مفتاح الوصول الخاص بالمتجر 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: عنوان رسالة البريد الإلكتروني التي سنرسلها.
-
Tex
t
: نص رسالة البريد الإلكتروني التي سترسله، يجب أن يكون نصا فقط -
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.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. وطبعًا يمكنك توسيع الخدمات التي تقدمها إلى خدمات أخرى يطلبها أصحاب المتاجر كما أشرنا وهنا نترك الأمر لك ولإبداعك.
تهانينا، لقد أتممت بناء تطبيق مفيد بإمكانك استعماله بطريقة تجارية إن أحببت وﻹطلاق شركتك الناشئة العاملة في هذا المجال، ولم ﻻ توسعها لتشمل منصات أكثر غير منصة زد فقط! نرجو لك كل التوفيق في رحلتك القادمة.
أفضل التعليقات
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.