ITSkillsCenter
الأعمال الرقمية

دفع Mobile Money في Moodle: Wave وOrange Money: درس 2026

6 دقائق للقراءة

📍 المقالة الرئيسية للمجموعة: EdTech فرنكوفونية 2026.

الجامعة الخاصة أو منصة التكوين المهني تطلب رسوم. الطالب يتعامل مع PayPal أو بطاقة دولية = استبعاد 70% من الجمهور المحلي. تكامل Wave وOrange Money في Moodle = enrolment تلقائي عند الدفع، تجربة مستخدم سلسة، إدارة فواتير مؤتمتة. هذا الدرس يفصل التنفيذ الكامل: plugin Moodle Wave، webhooks، تكامل OM، تسوية يومية.

المتطلبات

Moodle 4.x في الإنتاج. حساب Wave Business (راجع دفع Wave). حساب Orange Money Business. plugin Moodle مخصص أو استخدم plugin مجتمعي. المستوى: متقدم. الوقت: 4-6 ساعات.

الخطوة 1 — تفعيل Enrolment payment

Moodle لديه enrolment method «PayPal» مدمج، لكن لا Wave/OM. يجب بناء plugin مخصص أو استخدام webhook.

Site administration → Plugins → Enrolments → Manage enrol plugins
Activate "Pay" (Moodle 4.0+)
أو "External database"

الخطوة 2 — Plugin enrol_wave

إنشاء plugin بسيط في /var/www/elearning/enrol/wave/:

// version.php
<?php
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'enrol_wave';
$plugin->version = 2026042700;
$plugin->requires = 2024100100;  // Moodle 4.5

// lang/ar/enrol_wave.php
$string['pluginname'] = 'الدفع عبر Wave Mobile Money';
$string['enrolname'] = 'Wave';
$string['cost'] = 'تكلفة الدورة';
$string['currency'] = 'العملة';

// settings.php
$settings->add(new admin_setting_configtext('enrol_wave/api_key',
    'Wave API Key', 'مفتاح API من Wave Business', '', PARAM_TEXT));
$settings->add(new admin_setting_configtext('enrol_wave/webhook_secret',
    'Webhook Secret', '', '', PARAM_TEXT));

الخطوة 3 — صفحة الدفع

enrol/wave/pay.php:

require('../../config.php');
require_login();

$courseid = required_param('id', PARAM_INT);
$course = $DB->get_record('course', ['id' => $courseid], '*', MUST_EXIST);
$cost = $DB->get_field('enrol', 'cost', ['courseid' => $courseid, 'enrol' => 'wave']);

// إنشاء Wave checkout session
$ch = curl_init('https://api.wave.com/v1/checkout/sessions');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . get_config('enrol_wave', 'api_key'),
        'Content-Type: application/json',
        'idempotency-key: enrol-' . $USER->id . '-' . $courseid
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount' => (string)$cost,
        'currency' => 'XOF',
        'success_url' => $CFG->wwwroot . '/enrol/wave/success.php?course=' . $courseid,
        'error_url' => $CFG->wwwroot . '/enrol/wave/error.php',
        'client_reference' => 'user-' . $USER->id . '-course-' . $courseid
    ])
]);
$response = curl_exec($ch);
$session = json_decode($response);

// تسجيل في DB Moodle
$record = new stdClass();
$record->userid = $USER->id;
$record->courseid = $courseid;
$record->wave_session_id = $session->id;
$record->amount = $cost;
$record->status = 'pending';
$record->created_at = time();
$DB->insert_record('enrol_wave_payments', $record);

// إعادة توجيه إلى Wave
redirect($session->wave_launch_url);

الخطوة 4 — Webhook استقبال التأكيد

enrol/wave/webhook.php:

define('NO_MOODLE_COOKIES', true);
require('../../config.php');

$payload = file_get_contents('php://input');
$signature = isset($_SERVER['HTTP_WAVE_SIGNATURE']) ? $_SERVER['HTTP_WAVE_SIGNATURE'] : '';

// التحقق HMAC
$expected = hash_hmac('sha256', $payload, get_config('enrol_wave', 'webhook_secret'));
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    die('Invalid signature');
}

$event = json_decode($payload);

if ($event->type === 'checkout.session.completed') {
    $session_id = $event->data->id;
    
    // ابحث في DB
    $payment = $DB->get_record('enrol_wave_payments', ['wave_session_id' => $session_id]);
    if (!$payment) {
        http_response_code(404);
        die('Payment not found');
    }
    
    // تحديث حالة
    $payment->status = 'paid';
    $payment->paid_at = time();
    $DB->update_record('enrol_wave_payments', $payment);
    
    // التسجيل التلقائي في الدورة
    $enrol = enrol_get_plugin('wave');
    $instance = $DB->get_record('enrol', ['courseid' => $payment->courseid, 'enrol' => 'wave']);
    $enrol->enrol_user($instance, $payment->userid, 5, time(), time() + 31536000);  // 1 سنة
    
    // إرسال SMS تأكيد
    $user = $DB->get_record('user', ['id' => $payment->userid]);
    send_sms_confirmation($user->phone1, $payment->courseid);
}

http_response_code(200);
echo 'OK';

الخطوة 5 — صفحة النجاح

enrol/wave/success.php:

require('../../config.php');
require_login();
$courseid = required_param('course', PARAM_INT);

// تحقق من التسجيل
$is_enrolled = is_enrolled(context_course::instance($courseid), $USER);

if ($is_enrolled) {
    redirect(new moodle_url('/course/view.php', ['id' => $courseid]),
        'تم التسجيل بنجاح. مرحباً في الدورة!', null, \core\output\notification::NOTIFY_SUCCESS);
} else {
    // الدفع قيد المعالجة (webhook لم يصل بعد)
    echo $OUTPUT->header();
    echo '<p>دفعك قيد المعالجة. ستتلقى تأكيداً في غضون 5 دقائق.</p>';
    echo $OUTPUT->footer();
}

الخطوة 6 — تكامل Orange Money

OM API مماثل لـ Wave لكن مع OAuth2 token. تكوين plugin مماثل:

// pay.php Orange Money
$token_response = curl_post('https://api.orange.com/oauth/v3/token', [
    'grant_type' => 'client_credentials'
], [
    'Authorization: Basic ' . base64_encode($client_id . ':' . $client_secret)
]);
$token = json_decode($token_response)->access_token;

$payment_response = curl_post('https://api.orange.com/orange-money-webpay/v1/webpayment', [
    'merchant_key' => $merchant_key,
    'currency' => 'XOF',
    'order_id' => 'course-' . $courseid . '-user-' . $USER->id,
    'amount' => $cost,
    'return_url' => $success_url,
    'cancel_url' => $cancel_url,
    'notif_url' => $webhook_url,
    'lang' => 'ar'
], [
    'Authorization: Bearer ' . $token,
    'Content-Type: application/json'
]);

$payment_data = json_decode($payment_response);
redirect($payment_data->payment_url);

الخطوة 7 — اختيار طريقة الدفع

عرض زرين «دفع بـ Wave» و «دفع بـ Orange Money» للطالب. كل واحد يولد session منفصل.

// في صفحة enrolment
<a href="enrol/wave/pay.php?id={$course->id}" class="btn btn-primary">
  <img src="/wave-logo.png" /> دفع بـ Wave
</a>
<a href="enrol/orange/pay.php?id={$course->id}" class="btn btn-warning">
  <img src="/om-logo.png" /> دفع بـ Orange Money
</a>

الخطوة 8 — تسوية يومية

سكربت Moodle يقارن دفعات Wave/OM مع enrolments DB. يكشف الانحرافات.

// admin/cli/wave_reconciliation.php
define('CLI_SCRIPT', true);
require(__DIR__.'/../../config.php');

$date = date('Y-m-d', strtotime('-1 day'));

// جلب transactions Wave من API
$wave_txs = curl_get('https://api.wave.com/v1/transactions?date=' . $date);

// جلب payments Moodle
$moodle_payments = $DB->get_records_sql(
    "SELECT * FROM {enrol_wave_payments} WHERE DATE(FROM_UNIXTIME(paid_at)) = ?",
    [$date]
);

$matched = 0;
$unmatched_wave = [];
$unmatched_moodle = [];

foreach ($wave_txs as $tx) {
    $found = false;
    foreach ($moodle_payments as $mp) {
        if ($mp->wave_session_id === $tx->session_id) {
            $matched++;
            $found = true;
            break;
        }
    }
    if (!$found) $unmatched_wave[] = $tx;
}

echo "تطابق: $matched\n";
echo "Wave غير متطابق: " . count($unmatched_wave) . "\n";

// إرسال تقرير email للمحاسب
mail('comptable@universite.com', 'تسوية Wave ' . $date, ...);

الخطوة 9 — الفواتير الإلكترونية

توليد PDF فاتورة بعد الدفع. إرسال بالبريد إلى الطالب.

require_once($CFG->libdir.'/pdflib.php');

$pdf = new pdf();
$pdf->AddPage();
$pdf->SetFont('helvetica', 'B', 16);
$pdf->Cell(0, 10, 'فاتورة - Université Cheikh Anta Diop', 0, 1);

$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 10, 'الطالب: ' . $user->firstname . ' ' . $user->lastname, 0, 1);
$pdf->Cell(0, 10, 'الدورة: ' . $course->fullname, 0, 1);
$pdf->Cell(0, 10, 'المبلغ: ' . $payment->amount . ' XOF', 0, 1);
$pdf->Cell(0, 10, 'طريقة الدفع: ' . ($payment->method === 'wave' ? 'Wave' : 'Orange Money'), 0, 1);
$pdf->Cell(0, 10, 'تاريخ: ' . date('Y-m-d', $payment->paid_at), 0, 1);

$pdf->Output('facture.pdf', 'F');

// إرسال بالبريد
email_to_user($user, get_admin(), 'فاتورة دورتك', 'مرفقة بهذا البريد', '', $invoice_path);

الخطوة 10 — Reporting

Dashboard Moodle يعرض إحصائيات الدفعات: إجمالي الإيرادات، % نجاح، أكثر الدورات شعبية.

SELECT 
    c.fullname,
    COUNT(p.id) as enrolments,
    SUM(p.amount) as total_revenue
FROM mdl_enrol_wave_payments p
JOIN mdl_course c ON c.id = p.courseid
WHERE p.status = 'paid'
  AND p.paid_at > UNIX_TIMESTAMP() - 30*86400
GROUP BY c.id
ORDER BY total_revenue DESC
LIMIT 10;

الأخطاء الشائعة

الخطأ السبب الحل
Webhook 403 signature خاطئة تحقق raw body + HMAC
التسجيل مكرر webhook مرسَل مرتين idempotency check
Wave session expire 30 دقيقة default تجديد عند العودة
OM token expire 1h default cache + refresh
SMS لا يصل Twilio rate limit queue + retry
تسوية انحراف طلب pending قديم cleanup > 24h

التكيف مع السياق

أربع توضيحات. التوقيع التجاري الجامعي. الجامعة العمومية لا تطلب رسوم. الخاصة تطلب 50,000-500,000 XOF/سيمستر. تكامل بطاقات بنكية أيضاً (CMI، CIB) للوالدين بدون Wave/OM. التسوية المحاسبية. Wave/OM يحوّلون المبالغ يومياً. ربط حساب البنك = تجنب أن يكون رصيد كبير على مزود الدفع. Refunds. الانسحاب من الدورة قبل 7 أيام = استرداد كامل. التسوية مع Wave Refund API. Promotion codes. الجامعة تنشئ codes خصم 20% للأقدم. تكامل في Moodle Coupons plugin.

دروس الإخوة

الأسئلة المتكررة

Plugin مجاني؟ هذا الدرس يقدم plugin مفتوح المصدر مخصص. اكتبه مرة، استخدمه دائماً.

عمولة؟ Wave 1%، OM 1.5%. للدورة 50,000 XOF = 500-750 XOF عمولة. مقابل بطاقة 3% = 1,500 XOF.

اختبار قبل الإنتاج؟ Wave + OM لديهم sandbox. اختبر التدفق الكامل قبل publishing.

Refunds جزئية؟ Wave يدعم. OM يتطلب طلبات معقدة.

للاستزادة

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité