رحلتي في بناء تطبيق تسوق إلكتروني أصيل لمنصة WooCommerce خلال جائحة كوفيد-19
في مارس 2020، ومع إعلان الحكومات عن إغلاق المتاجر التي تبيع السلع غير الأساسية، شهد العالم تحولاً جذرياً في سلوك المستهلكين نحو التسوق عبر الإنترنت. تشير تحليلات Adobe Analytics إلى أن المبيعات عبر الإنترنت في الولايات المتحدة زادت بنسبة 49% في أبريل مقارنة بالعام السابق. لم يكن هذا الارتفاع ملحوظاً بشكل خاص على أجهزة الكمبيوتر المكتبية، لكن تطبيقات التسوق عبر الجوال مثل Instacart شهدت زيادة مذهلة بلغت حوالي 650% في عدد المستخدمين الجدد (في الولايات المتحدة وحدها) بين مارس وأبريل.
هذه الأرقام تؤكد حقيقة لا جدال فيها: لكي تستفيد متاجر التجارة الإلكترونية بشكل كامل من الزيادة الهائلة في أعداد المتسوقين عبر الإنترنت خلال جائحة فيروس كورونا، يجب أن تمتلك تطبيق جوال أصيل (Native Mobile App). في هذا الدليل الشامل، سأوضح لكم كيفية تطوير تطبيق Android أصيل للتسوق باستخدام منصة WooCommerce. سأشرح أيضاً الأسباب التي دفعتني لاختيار التطبيق الأصيل بدلاً من التطبيقات الهجينة (Hybrid Apps) أو تطبيقات الويب التقدمية (Progressive Web Apps)، ولماذا وقع اختياري على WooCommerce. والأفضل من ذلك، سأبدأ بتقديم بعض البيانات البحثية التي تبرز الأهمية القصوى لوجود تطبيق جوال لأي متجر إلكتروني.
تلاشي حركة الزوار إلى المتاجر التقليدية: الحاجة الملحة للتحول الرقمي
لقد أدت جائحة COVID-19 إلى تغييرات عميقة في عادات التسوق. تشير استطلاعات المستهلكين إلى أن التحول نحو التسوق عبر الإنترنت سيستمر طالما ظل فيروس كورونا يشكل تهديداً. كشف استطلاع أُجري على 1200 مستهلك في أواخر مارس 2020 أن 90% من المتسوقين كانوا مترددين في التسوق من المتاجر الفعلية بسبب الجائحة، وتوقع 45% منهم أن يصبح التسوق عبر الإنترنت ضرورة لهم خلال الأزمة.
وفي استطلاع منفصل أُجري في أبريل، أفاد 55% من المستهلكين عبر الإنترنت أنهم يطلبون المزيد عبر الإنترنت مما كانوا عليه قبل انتشار الفيروس، بزيادة من 26% في مارس. وقال 22% في أبريل إنهم يطلبون كميات أكبر بكثير عبر الإنترنت، مقارنة بـ 6% فقط في مارس. إذا استمرت الجائحة، فقد نشهد تحولاً كبيراً في التسوق عبر الإنترنت خلال موسمي التسوق الأكثر أهمية لتجار التجزئة: العودة إلى المدارس وموسم الأعياد.
خطط 23% من تجار التجزئة الذين شملهم الاستطلاع في مارس لتحويل مواردهم نتيجة لتفشي فيروس كورونا. لقد أُجبر قطاع التجزئة على إجراء تغييرات جذرية، حيث طُلب من جميع الشركات غير الأساسية الإغلاق، وتلاشت حركة الزوار إلى متاجر التجزئة التقليدية. حتى مع إعادة الفتح التدريجي، أصبحت أهمية وجود وظائف التجارة الإلكترونية أوضح من أي وقت مضى. لقد أثبتت الجائحة حاجة الشركات بجميع أنواعها إلى المرونة والاستعداد لأي طارئ، وضرورة قيامها بالقفزة الرقمية. ستكون التجارة الإلكترونية في صدارة المشهد في الوضع الطبيعي الجديد.

لماذا التطبيقات الأصلية ضرورية للمتاجر الإلكترونية؟
قد يقول البعض: «لكن عميلي يمتلك موقعاً إلكترونياً متجاوباً (Responsive Website)!» هذا صحيح، لكن هناك عدد من الاتجاهات المتضافرة التي تدفع أصحاب المواقع نحو امتلاك تطبيق جوال أصيل. الهدف الرئيسي لكل صاحب موقع هو إضافة قيمة لعمله من خلال الوصول إلى المزيد من المستخدمين. ليس سراً أن استخدام الهواتف المحمولة في ازدياد، وأن المزيد من الناس يتفاعلون مع هواتفهم المحمولة أكثر من أجهزة الكمبيوتر المكتبية.
للاستفادة من سوق مستخدمي الهواتف المحمولة هذا، نحتاج إلى معرفة أنواع التطبيقات التي يتفاعل معها هؤلاء المستخدمون أكثر من غيرها (تطبيقات الويب التقدمية، التطبيقات الهجينة، مواقع الويب المتجاوبة، أو التطبيقات الأصلية). لحسن الحظ، أجرى خبراء Go-Globe تحليلاً أظهر أن 85% من مستخدمي الجوال يتفاعلون مع التطبيقات الأصلية أكثر من غيرها. هناك عدد من الأسباب الأخرى التي تدعو الشركات إلى امتلاك تطبيق جوال أصيل لتلبية احتياجات العدد المتزايد من المتسوقين عبر الإنترنت في ظل جائحة كورونا. دعنا نستكشفها بمزيد من التفصيل:
تجربة مستخدم محسّنة للزوار الأوفياء
تُعد مواقع الويب المتنقلة رائعة للاكتشاف الأولي، ولكن المستخدمين الأوفياء – أولئك الذين يعودون مراراً وتكراراً – يرغبون في الحصول على تطبيق. يستخدم الناس التطبيقات أكثر مما يستخدمون البحث على الأجهزة المحمولة. وجود علامة تجارية (أي تطبيق) على الشاشة الرئيسية للمستخدم هو تذكير دائم بالموقع ومحتواه، مما يعزز الولاء ويشجع على التفاعل المتكرر.
تواصل مباشر وفعّال مع العملاء
يمكن للعلامة التجارية أن تكون موجودة على جميع قنوات التواصل الاجتماعي، ولكن جزءاً صغيراً فقط من المستخدمين سيرون رسالتها. يمكن أيضاً إرسال رسائل البريد الإلكتروني، ولكن بمعدل فتح يبلغ 25%، لن يتم الوصول إلا إلى جزء صغير من الجمهور. يمنح تطبيق الجوال الخاص بالعلامة التجارية خطاً مباشراً للمستخدمين، مما يؤدي في النهاية إلى الاحتفاظ بالمستخدمين وتحويل الزوار العاديين إلى عملاء أوفياء.
استغلال كامل لميزات الجهاز المحمول
تتمتع تطبيقات الجوال الأصلية بميزة استخدام ميزات الهاتف المحمول مثل الكاميرا، قائمة جهات الاتصال، نظام تحديد المواقع العالمي (GPS)، المكالمات الهاتفية، مقياس التسارع (accelerometer)، البوصلة، وغيرها. هذه الميزات، عند استخدامها داخل التطبيق، يمكن أن تجعل تجربة المستخدم تفاعلية وممتعة. علاوة على ذلك، يمكن لهذه الميزات أن تقلل من الجهود التي سيتعين على المستخدمين بذلها بخلاف ذلك. على سبيل المثال، يمكن للمستخدمين الذين يرغبون في معرفة موقع عمل تجاري استخدام GPS/الملاحة للعثور عليه بسهولة (مفيد بشكل خاص في تطبيقات الطعام). يمكن لهذه الميزات أيضاً تقصير الوقت الذي يستغرقه المستخدمون لأداء مهمة معينة في التطبيق بشكل كبير، ويمكن أن تعزز التحويلات. في حين أن مواقع الويب المتنقلة، وتطبيقات الويب التقدمية (PWAs)، والتطبيقات الهجينة يمكنها أيضاً استخدام بعض ميزات الجهاز، إلا أن هناك قيوداً تكنولوجية في استخدام جميع ميزات الجهاز التي يمكن لتطبيقات الجوال الأصلية استخدامها بسهولة.
القدرة على العمل دون اتصال بالإنترنت
ربما يكون هذا هو الاختلاف الأساسي بين موقع الويب المتنقل والتطبيق. على الرغم من أن تطبيقات الجوال الأصلية قد تتطلب اتصالاً بالإنترنت لأداء معظم مهامها، إلا أنها لا تزال قادرة على تقديم محتوى ووظائف أساسية للمستخدمين في وضع عدم الاتصال. لنأخذ مثال موقع التجارة الإلكترونية – يمكن للتطبيق توفير ميزات مثل حساب الضرائب والأقساط، وتحديد حد إنفاق المستخدم. يمكن أن تعمل هذه الميزات حتى بدون مساعدة اتصال بالإنترنت. على الرغم من أن مواقع الويب المتنقلة، وتطبيقات الويب التقدمية (PWAs)، وتطبيقات الجوال الهجينة يمكنها استخدام التخزين المؤقت (caching) لتحميل صفحات الويب بدون اتصال بالإنترنت، إلا أنها لا تستطيع تقديم سوى وظائف محدودة.
تعزيز حضور العلامة التجارية
يقضي المستخدمون قدراً كبيراً من وقتهم على الأجهزة المحمولة. من الآمن القول إن العديد من المستخدمين يرون التطبيقات التي قاموا بتثبيتها على أجهزتهم كل يوم تقريباً. يمكن اعتبار هذا اللقاء المنتظم فرصة لتعزيز العلامة التجارية للتطبيقات. حتى عندما لا يستخدم المستخدمون تطبيق جوال بنشاط، فإنهم لا يزالون يتذكرون العلامة التجارية المرتبطة بالتطبيق. يعمل رمز التطبيق كإعلان مصغر للعلامة التجارية. يساعد وجود تطبيق على جهاز المستخدم في التأثير على تصورهم للعلامة التجارية بشكل لا شعوري. يمكن ربط هذا السلوك بنظرية الكشف عن الإشارة (Signal Detection Theory)، التي تشير إلى أن المستخدمين لا يزالون يعالجون الإعلانات التي تجاهلوها على مستوى ما بشكل لا شعوري.
سرعة الأداء الفائقة مقارنة بالمواقع
يمكن لتطبيق الجوال المصمم جيداً أداء الإجراءات بشكل أسرع بكثير من موقع الويب المتنقل. تخزن التطبيقات عادةً بياناتها محلياً على الأجهزة المحمولة، على عكس مواقع الويب التي تستخدم بشكل عام خوادم الويب. لهذا السبب، يتم استرداد البيانات بسرعة في تطبيقات الجوال. يمكن للتطبيقات أيضاً توفير وقت المستخدمين عن طريق تخزين تفضيلاتهم واستخدامها لاتخاذ إجراءات استباقية نيابة عنهم. هناك أيضاً مبرر تقني لسبب قدرة تطبيقات الجوال على العمل بشكل أسرع. تستخدم مواقع الويب المتنقلة كود JavaScript لأداء معظم وظائفها. وبعض الأطر (frameworks) التي تستخدمها تطبيقات الجوال يمكن أن تعمل أسرع بخمس مرات تقريباً من JavaScript! بينما يحدث كل هذا في الخلفية، يتمكن المستخدمون من إكمال الإجراءات بسرعة أكبر في الواجهة الأمامية لتطبيقات الجوال، مما يساهم مرة أخرى في تجربة مستخدم ممتعة.
زيادة إمكانات تحسين محركات البحث (SEO)
يمكن أن تكون تطبيقات الجوال مفيدة بطريقتين: للمحتوى داخل التطبيق ومحتوى موقع الويب، حيث سيتم استخدام كلمات مرادفة في المحتوى للمنتجات والخدمات. تصنف Google في الوقت الحاضر المحتوى داخل التطبيق أيضاً، ويمكنك تعديل المحتوى في تطبيقك لمساعدتك في تحسين محركات البحث لموقع الويب الخاص بك.
ارتفاع الإنفاق على تطبيقات الجوال
من المتوقع أن يتضاعف الإنفاق على تطبيقات الجوال بحلول عام 2024، على الرغم من الآثار الاقتصادية لجائحة COVID-19. وفقاً لتوقعات السوق المعدلة للفترة 2020-2024، من المتوقع أن يصل إنفاق المستهلكين العالمي على تطبيقات الجوال إلى 171 مليار دولار بحلول عام 2024. هذا أكثر من ضعف الـ 85 مليار دولار من عام 2019. ومع ذلك، يقل هذا الإجمالي بحوالي 3 مليارات دولار (أو 2%) عن التوقعات التي أصدرتها الشركة قبل تفشي COVID-19.

ومع ذلك، من الجدير بالذكر أنه حتى المناطق الأبطأ نمواً على كل من متجر تطبيقات Apple و Google Play ستشهد إيرادات أعلى بأكثر من 80% من مستوياتها لعام 2019 بحلول عام 2024. ستحقق متاجر التطبيقات أيضاً العديد من الإنجازات خلال السنوات الخمس القادمة. ففي البداية، سيتجاوز الإنفاق العالمي على تطبيقات الجوال 100 مليار دولار لأول مرة في عام 2020، لينمو بنسبة 20% تقريباً على أساس سنوي ليصل إلى 102 مليار دولار. يشير هذا إلى أن أصحاب المواقع لا يحصلون فقط على المزيد من العملاء، بل على المزيد من العملاء الذين يدفعون ومستعدون للإنفاق.
الوصول إلى جمهور أصغر سناً
تشير الإحصائيات إلى أن الفئة العمرية من 18 إلى 24 عاماً هي الأكثر نشاطاً في استخدام تطبيقات الجوال. هل ما زلت غير مقتنع؟ دع هذه الإحصائيات تتحدث:
- 77% من الأمريكيين يمتلكون هاتفاً ذكياً.
- أكثر من 230 مليون مستهلك أمريكي يمتلكون هواتف ذكية.
- حوالي 100 مليون مستهلك أمريكي يمتلكون أجهزة لوحية (
tablets). - 79% من مستخدمي الهواتف الذكية أجروا عملية شراء عبر الإنترنت باستخدام أجهزتهم المحمولة في الأشهر الستة الماضية.
- تشكل دولارات التجارة الإلكترونية الآن 10% من إجمالي إيرادات التجزئة.
- 80% من المتسوقين استخدموا هاتفاً محمولاً داخل متجر فعلي للبحث عن مراجعات المنتجات، أو مقارنة الأسعار، أو العثور على مواقع متاجر بديلة.
- يُقدر أن هناك 10 مليارات جهاز متصل بالهاتف المحمول قيد الاستخدام حالياً.
- يقضي مستخدمو تطبيقات الجوال ما متوسطه 201.8 دقيقة شهرياً في التسوق، مقارنة بـ 10.9 دقيقة شهرياً لمستخدمي مواقع الويب.
- 58% من جيل الألفية ذكروا أنهم يفضلون الشراء عبر التطبيقات.
تجاهل هذه الاتجاهات في تطور التجارة الإلكترونية عبر الجوال (المشار إليها في الصناعة باسم m-Commerce) يعني احتمال تفويت المزيد والمزيد من الأرباح مع استمرار هذه الاتجاهات.
الشروع في العمل: بناء التطبيق خطوة بخطوة
جميع تطبيقات الجوال الأصلية هي ببساطة حزمة من الأكواد المكتوبة بلغات مثل Java أو Kotlin أو Objective-C أو Swift، والتي تعالج البيانات والموارد (مثل ملفات .png و .xml). يمكن استرداد البيانات المعالجة من مستشعرات الجهاز المحمول مثل الشاشة، الكاميرا، ذاكرة التخزين، GPS، مكبرات الصوت، مقياس التسارع، البوصلة، أو من خادم (server).
الأدوات والمكونات الأساسية
في هذا الدليل، سنستخدم الأدوات التالية:
JSoup(مكتبةJava):JSoupهو محللHTMLيمكنه تحليل عنوانURLمباشرةً، ومحتوى نصHTML، ويوفر مجموعة من واجهات برمجة التطبيقات (API) المريحة جداً لمعالجة البيانات.Android Studio: هذه هي الأداة الرسمية لكتابة وتجميع تطبيقاتAndroidبلغةJavaأوKotlin، وتنتج ملفات.apkجاهزة للتثبيت. سيتم كتابة جميع الأكواد في هذا الدليل بلغةJava.- إضافة
WooCommerce: هذه هي أشهر إضافة للتجارة الإلكترونية لمنصةWordPress. لذا، فإن بناء تطبيق جوال لأشهر إضافة للتجارة الإلكترونية يبدو فكرة رائعة.
الحصول على البيانات ومعالجتها من الخادم
سيتم الحصول على البيانات من الخادم باستخدام واجهة برمجة تطبيقات RESTful API. تتكامل WooCommerce (الإصدار 2.6 فما فوق) بشكل كامل مع WordPress REST API. يتيح ذلك إنشاء البيانات وقراءتها وتحديثها وحذفها باستخدام طلبات بتنسيق JSON. تستخدم هذه الواجهة طرق مصادقة WordPress REST API Authentication وأفعال HTTP القياسية التي تفهمها معظم عملاء HTTP.
سأستخدم الإصدار 2 من واجهة برمجة التطبيقات (v2)، وهو متاح لـ WooCommerce الإصدار 3.0.x أو أحدث و WordPress الإصدار 4.4 أو أحدث. تنسيق الاستجابة الافتراضي هو JSON. ستُرجع الطلبات الناجحة حالة HTTP من نوع 200 OK. فيما يلي بعض المعلومات الأساسية حول الاستجابات:
- تُرجع التواريخ بتنسيق
ISO8601:YYYY-MM-DDTHH:MM:SS. - تُرجع معرفات الموارد (
Resource IDs) كأعداد صحيحة. - أي مبلغ مالي عشري، مثل الأسعار أو الإجماليات، سيُرجع كسلاسل نصية (
strings) تحتوي على منزلتين عشريتين. - تُرجع المبالغ الأخرى، مثل عدد العناصر، كأعداد صحيحة.
- تُضمن الحقول الفارغة بشكل عام كقيمة
nullأو سلسلة نصية فارغة بدلاً من حذفها.
يجب مصادقة معظم الطلبات المقدمة إلى WooCommerce REST API باستخدام مفاتيح تم إنشاؤها مسبقاً (consumer key و consumer secret). يتم إنشاء مفاتيح جديدة من خلال واجهة إدارة WordPress. ما عليك سوى الانتقال إلى WooCommerce > Settings > API > Keys/Apps.

انقر على زر "إضافة مفتاح" (Add Key). في الشاشة التالية، أضف وصفاً واختر مستخدم WordPress الذي ترغب في إنشاء المفتاح له. ثم انقر على زر "إنشاء مفتاح API" (Generate API Key) وسيقوم WooCommerce بإنشاء مفاتيح REST API للمستخدم المحدد.

الآن بعد أن تم إنشاء المفاتيح، يجب أن ترى مفتاحين جديدين. هذان المفتاحان هما Consumer Key و Consumer Secret الخاصين بك.

بعد ذلك، نقوم بإنشاء عنوان URL يتضمن موقع الويب الخاص بالعمل ونقطة نهاية API التي ستُرجع البيانات بتنسيق JSON. باستخدام مكتبة JSoup، نتصل بعنوان URL (موقع الويب + نقطة نهاية API) ونضيف مفاتيح API (consumer key و consumer secret) مع معلمات أخرى لنقطة نهاية API. تقبل جميع نقاط النهاية تقريباً معلمات اختيارية يمكن تمريرها كمعلمة سلسلة استعلام HTTP:
public static final int JSOUP_CONNECTION_TIMEOUT = 100000 ;
String websiteUrl = "www.freecodecamp.shop" ; //example website doesn't exist
//This API lets you retrieve all product categories
String apiExtension = "/wp-json/wc/v2/products/categories" ;
//Map to store our parameters in a key value format
HashMap<String, String> data = new HashMap<>();
data.put( "page" , String.valueOf(page)); // API parameter to get the current page of the collection. Default is 1.
//add API keys for authentication
data.put( "consumer_key" , getKey());
data.put( "consumer_secret" , getSecret());
//concatenate both websiteUrl and apiExtension to form the requestUrl
String requestUrl = websiteUrl + apiExtension
try {
Connection.Response response = Jsoup.connect(requestUrl).timeout(JSOUP_CONNECTION_TIMEOUT).followRedirects( true )
.ignoreContentType( true )
.data(data)
.execute();
String json = response.body();
//parse json string to get needed data.
} catch (Exception e){
//catch both JSONException and IOException
}
تقوم طريقتي getKey() و getSecret() ببساطة بإرجاع مفاتيح API:
public static String getKey () {
return "ck_a89d59d7441f027df0d91f01c9e2dcaxxxxxxxxx" ;
}
public static String getSecret () {
return "cs_c3f8fe620bd5b1cb3567712eb843609xxxxxxxxx" ;
}
لكن انتظر… بالنسبة لبعض مواقع الويب، عند تشغيل هذا الكود، فإنه يعطيني الخطأ 401 Unauthorized. هذا خطأ مصادقة أو إذن، بسبب مفاتيح API غير صحيحة… فما السبب؟
بالتأكيد، الكود أعلاه يعمل فقط لمواقع الويب المؤمنة ببروتوكول HTTPS، بينما مواقع الويب غير المؤمنة التي تستخدم بروتوكول HTTP ستحتاج إلى تشفير مفاتيح API قبل إرسالها. ماذا يعني ذلك بالضبط؟
HTTPS هو HTTP مع تشفير، حيث يستخدم HTTPS بروتوكول TLS (SSL) لتشفير طلبات واستجابات HTTP العادية. هذا يجعل HTTPS أكثر أماناً بكثير من HTTP. موقع الويب الذي يستخدم HTTP يحتوي على http:// في عنوان URL الخاص به، بينما موقع الويب الذي يستخدم HTTPS يحتوي على https://.
يجب عليك استخدام مصادقة OAuth 1.0a "one-legged" لضمان عدم اعتراض بيانات اعتماد REST API الخاصة بك من قبل مهاجم. المعلمات المطلوبة هي _oauth_consumer_key و oauth_timestamp و oauth_nonce و oauth_signature و _oauth_signature_method. سنقوم بإنشاء طريقة ستحصل على هذه المعلمات المشفرة كـ HashMap. ستقبل الطريقة getAuthenticationPrams(String url, HashMap<String, String> mData) عنوان URL للطلب (عنوان URL للموقع + امتداد API) وأي معلمات قد نرغب في إضافتها إلى امتداد API. هنا نقوم بجمع وتطبيع معلماتنا، والتي تتضمن جميع معلمات oauth_* باستثناء _oauth_signature نفسه.
public static HashMap<String, String> getAuthenticationParams (String url, @Nullable HashMap<String, String> mData) {
HashMap<String, String> data = new HashMap<>();
if (url.startsWith( "http://" )){
String nonce = new TimestampService().getNonce();
String timestamp = new TimestampService().getTimestampInSeconds();
data.put( "oauth_consumer_key" , getKey());
data.put( "oauth_signature_method" , "HMAC-SHA1" );
data.put( "oauth_version" , "1.0" );
data.put( "oauth_nonce" , nonce);
data.put( "oauth_timestamp" , timestamp);
if (mData != null ) data.putAll(mData);
String firstBaseString = "GET&" + urlEncoded(url);
String generatedBaseString = formatQuery(data);
ParametersList result = new ParametersList();
result.addQuerystring(generatedBaseString);
generatedBaseString = result.sort().asOauthBaseString();
String secondBaseString = "&" + generatedBaseString;
if (firstBaseString.contains( "%3F" )) {
secondBaseString = "%26" + urlEncoded(generatedBaseString);
}
String baseString = firstBaseString + secondBaseString;
String signature = new HmacSha1SignatureService().getSignature(baseString, getSecret(), "" );
data.put( "oauth_signature" , signature);
} else {
data.put( "consumer_key" , getKey());
data.put( "consumer_secret" , getSecret());
data.putAll(mData);
}
return data;
}
تقوم فئة TimestampService بإنشاء طابع زمني (timestamp) وقيمة عشوائية لمرة واحدة (nonce) لمعلمتي _oauth_nonce و _oauth_timestamp.
import java.util.Random;
public class TimestampService {
private Timer timer;
/**
* Default constructor.
*/
public TimestampService () {
timer = new Timer();
}
public String getNonce () {
Long ts = getTs();
return String.valueOf(ts + timer.getRandomInteger());
}
public String getTimestampInSeconds () {
return String.valueOf(getTs());
}
private Long getTs () {
return timer.getMilis() / 1000 ;
}
void setTimer (Timer timer) {
this .timer = timer;
}
/**
* Inner class that uses { @link System} for generating the timestamps.
*
*/
static class Timer {
private final Random rand = new Random();
Long getMilis () {
return System.currentTimeMillis();
}
Integer getRandomInteger () {
return rand.nextInt();
}
}
}
تقوم طريقة formatQuery(HashMap<String, String> mData) بتنسيق المعلمات إلى معلمات استعلام (query parameters) وهي مجموعة من المعلمات المرفقة بنهاية عنوان URL. تقوم طريقة urlEncoded(String url) بترجمة السلسلة المعطاة إلى تنسيق application/x-www-form-urlencoded باستخدام مخطط ترميز محدد. تستخدم هذه الطريقة مخطط الترميز المقدم للحصول على البايتات للأحرف غير الآمنة.
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
private static String formatQuery (HashMap<String, String> mData) {
int i = 0 ;
StringBuilder param = new StringBuilder();
for (String key : mData.keySet()){
if (i > 0 ){
param.append( "&" );
}
param.append(key);
param.append( "=" );
param.append(mData.get(key));
i++;
}
return param.toString();
}
private static String urlEncoded (String url) {
String encodedurl = "" ;
try {
encodedurl = URLEncoder.encode(url, "UTF-8" );
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return encodedurl;
}
تأخذ فئة ParametersList الاستعلام المنسق ثم تقوم بترميزه باستخدام فئة OAuthEncoder. تحتاج القيم التي تبدأ بـ oauth_* إلى ترميزها في سلسلة نصية سيتم استخدامها لاحقاً. عملية بناء السلسلة محددة جداً:
- ترميز كل مفتاح وقيمة سيتم توقيعها بنسبة مئوية (
Percent encode). - فرز قائمة المعلمات أبجدياً حسب المفتاح المشفر.
- لكل زوج مفتاح/قيمة:
- إلحاق المفتاح المشفر بالسلسلة الناتجة.
- إلحاق الحرف
=بالسلسلة الناتجة. - إلحاق القيمة المشفرة بالسلسلة الناتجة.
- إذا كان هناك المزيد من أزواج المفتاح/القيمة المتبقية، قم بإلحاق الحرف
&بالسلسلة الناتجة.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class ParametersList {
private static final char QUERY_STRING_SEPARATOR = '?' ;
private static final String PARAM_SEPARATOR = "&" ;
private static final String PAIR_SEPARATOR = "=" ;
private static final String EMPTY_STRING = "" ;
private final List<Parameter> params;
public ParametersList () {
params = new ArrayList<Parameter>();
}
ParametersList(List<Parameter> params) {
this .params = new ArrayList<Parameter>(params);
}
public void add (String key, String value) {
params.add( new Parameter(key, value));
}
public String asOauthBaseString () {
return OAuthEncoder.encode(asFormUrlEncodedString());
}
public String asFormUrlEncodedString () {
if (params.size() == 0 ) return EMPTY_STRING;
StringBuilder builder = new StringBuilder();
for (Parameter p : params) {
builder.append( '&' ).append(p.asUrlEncodedPair());
}
return builder.toString().substring( 1 );
}
public void addAll (ParametersList other) {
params.addAll(other.params);
}
public void addQuerystring (String queryString) {
if (queryString != null && queryString.length() > 0 ) {
for (String param : queryString.split(PARAM_SEPARATOR)) {
String pair[] = param.split(PAIR_SEPARATOR);
String key = OAuthEncoder.decode(pair[ 0 ]);
String value = pair.length > 1 ? OAuthEncoder.decode(pair[ 1 ]) : EMPTY_STRING;
params.add( new Parameter(key, value));
}
}
}
public boolean contains (Parameter param) {
return params.contains(param);
}
public int size () {
return params.size();
}
public ParametersList sort () {
ParametersList sorted = new ParametersList(params);
Collections.sort(sorted.params);
return sorted;
}
}
public class Parameter implements Comparable < Parameter > {
private final String key;
private final String value;
public Parameter (String key, String value) {
this .key = key;
this .value = value;
}
public String asUrlEncodedPair () {
return OAuthEncoder.encode(key).concat( "=" ).concat(OAuthEncoder.encode(value));
}
public boolean equals (Object other) {
if (other == null ) return false ;
if (other == this ) return true ;
if (!(other instanceof Parameter)) return false ;
Parameter otherParam = (Parameter) other;
return otherParam.key.equals(key) && otherParam.value.equals(value);
}
public int hashCode () {
return key.hashCode() + value.hashCode();
}
public int compareTo (Parameter parameter) {
int keyDiff = key.compareTo(parameter.key);
return keyDiff != 0 ? keyDiff : value.compareTo(parameter.value);
}
}
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
public class OAuthEncoder {
private static String CHARSET = "UTF-8" ;
private static final Map<String, String> ENCODING_RULES;
static {
Map<String, String> rules = new HashMap<String, String>();
rules.put( "*" , "%2A" );
rules.put( "+" , "%20" );
rules.put( "%7E" , "~" );
ENCODING_RULES = Collections.unmodifiableMap(rules);
}
private OAuthEncoder () {}
public static String encode (String plain) {
String encoded = "" ;
try {
encoded = URLEncoder.encode(plain, CHARSET);
} catch (UnsupportedEncodingException uee) {
throw new OAuthException( "Charset not found while encoding string: " + CHARSET, uee);
}
for (Map.Entry<String, String> rule : ENCODING_RULES.entrySet()) {
encoded = applyRule(encoded, rule.getKey(), rule.getValue());
}
return encoded;
}
private static String applyRule (String encoded, String toReplace, String replacement) {
return encoded.replaceAll(Pattern.quote(toReplace), replacement);
}
public static String decode (String encoded) {
try {
return URLDecoder.decode(encoded, CHARSET);
} catch (UnsupportedEncodingException uee) {
throw new OAuthException( "Charset not found while decoding string: " + CHARSET, uee);
}
}
}
public class OAuthConstants {
private OAuthConstants () {}
public static final String OUT_OF_BAND = "oob" ;
}
public class OAuthException extends RuntimeException {
/**
* Default constructor
* @param message message explaining what went wrong
* @param e original exception
*/
public OAuthException (String message, Exception e) {
super (message, e);
}
/**
* No-exception constructor. Used when there is no original exception
*
* @param message message explaining what went wrong
*/
public OAuthException (String message) {
super (message, null );
}
private static final long serialVersionUID = 1L ;
}
يجب ضم القيم المجمعة (معلمات oauth_* + معلمات امتداد API) لتكوين سلسلة نصية واحدة، سيتم من خلالها إنشاء التوقيع. يُطلق على هذا اسم سلسلة أساس التوقيع (signature base string) في مواصفات OAuth. لترميز طريقة HTTP، وعنوان URL للطلب، وسلسلة المعلمات في سلسلة نصية واحدة:
- اجعل السلسلة الناتجة مساوية لطريقة
HTTPبالحروف الكبيرة (GETفي هذا المثال). - ألحق الحرف
&بالسلسلة الناتجة. - قم بترميز
URLبنسبة مئوية وألحقه بالسلسلة الناتجة. - ألحق الحرف
&بالسلسلة الناتجة. - قم بترميز سلسلة المعلمات بنسبة مئوية وألحقها بالسلسلة الناتجة.
تقوم فئة HmacSha1SignatureService بإنشاء التوقيع باستخدام سلسلة أساس التوقيع ومفتاح consumer secret الخاص بك مع الحرف & باستخدام خوارزمية التجزئة HMAC-SHA1.
import android.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class HmacSha1SignatureService {
private static final String EMPTY_STRING = "" ;
private static final String CARRIAGE_RETURN = "\r\n" ;
private static final String UTF8 = "UTF-8" ;
private static final String HMAC_SHA1 = "HmacSHA1" ;
private static final String METHOD = "HMAC-SHA1" ;
public String getSignature (String baseString, String apiSecret, String tokenSecret) {
try {
return doSign(baseString, OAuthEncoder.encode(apiSecret) + '&' + OAuthEncoder.encode(tokenSecret));
} catch (Exception e) {
throw new OAuthSignatureException(baseString, e);
}
}
private String doSign (String toSign, String keyString) throws Exception {
SecretKeySpec key = new SecretKeySpec((keyString).getBytes(UTF8), HMAC_SHA1);
Mac mac = Mac.getInstance(HMAC_SHA1);
mac.init(key);
byte [] bytes = mac.doFinal(toSign.getBytes(UTF8));
return bytesToBase64String(bytes).replace(CARRIAGE_RETURN, EMPTY_STRING);
}
private String bytesToBase64String ( byte [] bytes) {
return Base64.encodeToString(bytes,Base64.NO_WRAP);
}
public String getSignatureMethod () {
return METHOD;
}
}
/**
* Specialized exception that represents a problem in the signature
*/
public class OAuthSignatureException extends OAuthException {
private static final long serialVersionUID = 1L ;
private static final String MSG = "Error while signing string: %s" ;
/**
* Default constructor
*
* @param stringToSign plain string that gets signed (HMAC-SHA, etc)
* @param e original exception
*/
public OAuthSignatureException (String stringToSign, Exception e) {
super (String.format(MSG, stringToSign), e);
}
}
هذا كل شيء! الآن يمكنك الاتصال بأي موقع تجارة إلكترونية يستخدم إضافة WooCommerce دون تثبيت أي إضافة أخرى. باستخدام WordPress REST API، يمكنك الحصول على البيانات التي تريدها أو معالجتها باستخدام نقاط نهاية API الخاصة بها. نقاط النهاية التي استخدمتها هي:
/wp-json/wc/v2/customers– تتيح لك إنشاء عميل جديد بعد التحقق منه من خلال شاشة التسجيل/الدخول في التطبيق./wp-json/wc/v2/payment_gateways– تتيح لك استرداد وعرض جميع بوابات الدفع المتاحة./wp-json/wc/v2/products– تساعدك على عرض جميع المنتجات التي تم بيعها على الموقع./wp-json/wc/v2/shipping/zones– تساعدك على إنشاء منطقة شحن جديدة./wp-json/wc/v2/settings/general/woocommerce_currency– تحصل على العملة المستخدمة./wp-json/wc/v2/orders– تساعدك على إنشاء طلب جديد./wp-json/wc/v2/products/categories– تتيح لك استرداد جميع فئات المنتجات./wp-json/wc/v2/coupons– تساعدك على سرد جميع الكوبونات التي أنشأها مسؤول الموقع.
النتيجة النهائية: تطبيق الجوال الخاص بي
إليكم نظرة على التطبيق النهائي الذي قمت ببنائه:

موارد إضافية لتصميم واجهة المستخدم وتجربة المستخدم (UI/UX)
عند الانتهاء من إنشاء تطبيق الجوال الأصيل الخاص بك، ستحتاج إلى واجهة مستخدم/تجربة مستخدم (UI/UX) يتفاعل معها المستخدم لمعالجة البيانات التي تم الحصول عليها من خادم WordPress. لحسن الحظ، يقدم فريق Wsdesign بعض القوالب المجانية والجاهزة للاستخدام التي يمكنك تنزيلها. آمل أن يكون هذا المقال مفيداً لكم، وأن يكون قد ساعدكم في تعلم وبناء تطبيق رائع اليوم.
الخلاصة التقنية
تُظهر هذه الرحلة التقنية بوضوح أن بناء تطبيق جوال أصيل لمنصات التجارة الإلكترونية مثل WooCommerce ليس مجرد خيار، بل أصبح ضرورة استراتيجية، خاصة في أوقات الأزمات التي تدفع نحو التحول الرقمي. لقد أثبتت جائحة COVID-19 أن الاعتماد على المواقع المتجاوبة وحدها لم يعد كافياً لتلبية توقعات المستخدمين المتزايدة، الذين يفضلون تجربة سلسة وغنية بالميزات التي لا يمكن توفيرها إلا عبر التطبيقات الأصلية.
يكمن جوهر النجاح في فهم الفروقات الدقيقة بين بروتوكولات الاتصال (HTTP و HTTPS) ومتطلبات المصادقة المعقدة مثل OAuth 1.0a، والتي تضمن أمان البيانات. إن استخدام مكتبات قوية مثل JSoup وتبني منهجية RESTful API يفتح الأبواب أمام مرونة هائلة في جلب البيانات ومعالجتها. علاوة على ذلك، فإن القدرة على الاستفادة من ميزات الجهاز الأصلية، وتقديم تجربة أسرع، وتعزيز الوجود الرقمي للعلامة التجارية، كلها عوامل تساهم في تحقيق عوائد استثمارية كبيرة وزيادة ولاء العملاء. هذا المشروع لا يعرض فقط كيفية بناء تطبيق عملي، بل يؤكد أيضاً على أهمية التفكير الاستراتيجي في بنية التطبيق وأمنه لضمان تجربة مستخدم استثنائية وقبول واسع في السوق الرقمي.