في هذا الدرس سنقوم بإنشاء صفحة دفع إلكتروني باستخدام خدمة Stripe من خلال إطار Laravel، وسنقوم بمقارنة طريقتين مختلفتين للتكامل: عبر HTTP Client المباشر، وعبر الحزمة الرسمية الخاصة بـ Stripe. سنتعلم كيفية إنشاء PaymentIntent، إرسال المفاتيح الآمنة، ومعالجة الأخطاء بطريقة احترافية.
حيث سنحصل على الشكل التالي
🚨 سنجرب طريقتين مختلفتين للتكامل:
وسنقارن النتائج، ونشرح لماذا يُفضل غالبًا استخدام الحزمة الرسمية إن وُجدت.
بنهاية هذا الدرس، ستكون قادرًا على:
Stripe تستخدم نظام يُعرف بـ Payment Intents لمعالجة المدفوعات بطريقة مرنة وآمنة.
عند إرسال طلب لإنشاء عملية دفع، نحصل على قيمة client_secret وهي ما نحتاجه لإتمام الدفع من واجهة المستخدم.

// routes/web.php
Route::get('/payment', [PaymentController::class, 'payment'])->name('index.payment');use App\Services\PaymentService;
use Illuminate\Support\Facades\Log;
class PaymentController extends Controller
{
    // 🟡 هنا قمنا بعمل inject لـ PaymentService من خلال المُنشئ (Constructor)
    // هذا يسمح لنا باستخدامه في جميع دوال هذا الكلاس دون الحاجة لإنشاء كائن جديد يدوياً
    public function __construct(private PaymentService $paymentService) {}
    // 🟢 هذه الدالة مسؤولة عن عرض صفحة الدفع للمستخدم
    // يتم فيها إنشاء "Payment Intent" عبر Stripe وإرسال بياناته إلى الواجهة
    public function payment()
    {
        // 🔹 هنا ننشئ رقم طلب (order ID) مميز لكل عملية شراء
        $orderId = uniqid('book_');
        // 🔹 متغيرات لتخزين الخطأ وبيانات الدفع لاحقًا
        $error = null;
        $paymentData = null;
        try {
            // ✅ الخيار الأفضل: استخدام الحزمة الرسمية من Stripe لإنشاء عملية الدفع
            $clientSecret = $this->paymentService->createPaymentIntentWithPackage(5000, 'usd', $orderId);
            // ✅ تجهيز بيانات الدفع لإرسالها إلى الواجهة (Blade)
            $paymentData = [
                'client_secret'    => $clientSecret,
                'publishable_key'  => config('services.stripe.public'), // مفتاح النشر العام
                'amount'           => 50.00,
                'order_id'         => $orderId,
            ];
        } catch (\Exception $e) {
            // ⚠️ في حال حدوث أي خطأ أثناء إنشاء عملية الدفع، نقوم بتسجيل الخطأ
            Log::error('فشل في إنشاء عملية الدفع', [
                'error'     => $e->getMessage(),
                'order_id'  => $orderId,
            ]);
            // ثم نمرر رسالة الخطأ لعرضها في الواجهة
            $error = $e->getMessage();
        }
        // 🟣 نعيد عرض صفحة الدفع مع دمج البيانات (سواء بيانات الدفع أو رسالة الخطأ)
        return view('payment.form', array_merge($paymentData ?? [], ['error' => $error]));
    }
}هنا يجب إضافة مصفوفه جديد بإسم stripe مثلا من اجل، في ملف app/services.php
    'stripe' => [
        'secret' => env('STRIPE_SECRET_KEY'),
        'public' => env('STRIPE_PUBLISHABLE_KEY'),
    ],بالطبع يجب ان نقوم بوضع في ملف .env والتي يمكن الحصول عليها من الموقع الرسمي
STRIPE_SECRET_KEY= STRIPE_PUBLISHABLE_KEY=
🔐 المفتاح السري (Secret) يستخدم فقط في الخادم (Laravel)، أما المفتاح القابل للنشر (Publishable Key) فيُستخدم في الواجهة الأمامية (JavaScript) لتكوين عناصر الدفع.
php artisan make:class Services/PaymentService
namespace App\Services;
class PaymentService
{
    private $secretKey;
    public function __construct()
    {
        $this->secretKey = config('services.stripe.secret');
    }
    public function createPaymentIntent($amount, $currency = 'usd', $orderId = null): string
    {
        // سنشرح هذا لاحقًا
    }
    public function createPaymentIntentWithPackage($amount, $currency = 'usd', $orderId = null): string
    {
        // سنشرح هذا لاحقًا
    }
}1. التنفيذ
use Illuminate\Support\Facades\Http;
public function createPaymentIntent($amount, $currency = 'usd', $orderId = null): string
{
    // 🟡 إعداد رأس الطلب (Authorization Header)
    // نستخدم مفتاح Stripe السري (secret key) لتوثيق الطلب باستخدام Bearer Token
    $headers = [
        'Authorization' => 'Bearer ' . $this->secretKey,
    ];
    // 🟢 إرسال طلب POST إلى Stripe لإنشاء Payment Intent
    // نرسل معلومات الدفع مثل المبلغ، العملة، وتفاصيل الطلب
    $response = Http::withHeaders($headers)
        ->asForm() // ✅ مهم جداً: Stripe يتطلب محتوى بصيغة x-www-form-urlencoded
        ->post('https://api.stripe.com/v1/payment_intents', [
            'amount' => $amount, // Stripe يتطلب أن يكون المبلغ بالـ "cents" (مثلاً 5000 = 50.00$)
            'currency' => $currency, // العملة المستخدمة (USD أو غيرها)
            // ⚙️ تفعيل طرق الدفع التلقائية (بطاقات، Apple Pay، Google Pay...)
            'automatic_payment_methods[enabled]' => 'true',
            // 📝 معلومات إضافية عن الطلب، مثل رقم الطلب واسم المنتج
            'metadata[order_id]' => $orderId ?? uniqid('order_'),
            'metadata[product]' => 'Man On The Sun - Book',
        ]);
    // ✅ في حالة النجاح (status 200)، نستخرج client_secret من الاستجابة
    if ($response->successful()) {
        return $response->json()['client_secret'];
    }
    // ❌ في حالة وجود خطأ، نمرر الاستجابة لدالة خاصة بالتعامل مع الأخطاء
    $this->handleApiError($response);
}
private function handleApiError($response)
{
    $statusCode = $response->status();
    $errorData = $response->json();
    switch ($statusCode) {
        case 400:
            throw new \InvalidArgumentException($errorData['error']['message'] ?? 'طلب غير صالح');
        case 401:
            throw new \Exception('مفتاح API غير صالح');
        case 402:
            throw new \Exception($errorData['error']['decline_code'] ?? 'تم رفض الدفع');
        case 403:
            throw new \Exception('صلاحيات غير كافية');
        case 429:
            throw new \Exception('تم تجاوز حد الطلبات');
        default:
            throw new \Exception('خطأ غير معروف: ' . $response->body());
    }
}composer require stripe/stripe-php
// 🟡 تعريف كائن stripeClient الذي يمثل الاتصال بالحزمة الرسمية لـ Stripe
private $stripeClient;
public function __construct()
{
    // 🔐 تحميل مفتاح Stripe السري من ملف config/services.php
    $this->secretKey = config('services.stripe.secret');
    // 🧠 إنشاء كائن StripeClient باستخدام المفتاح السري
    // هذا الكائن يوفر كل وظائف Stripe (إنشاء دفعات، عملاء، استرداد، إلخ)
    $this->stripeClient = new \Stripe\StripeClient($this->secretKey);
}
public function createPaymentIntentWithPackage($amount, $currency = 'usd', $orderId = null): string
{
    try {
        // ✅ إنشاء PaymentIntent باستخدام الحزمة الرسمية
        $intent = $this->stripeClient->paymentIntents->create([
            'amount' => $amount, // Stripe يتعامل مع المبالغ بالـ "cents" (مثلاً 5000 = 50.00$)
            'currency' => $currency, // رمز العملة (مثل usd)
            // ✅ تفعيل ميزة الدفع التلقائي بطرق متعددة (بطاقة، Apple Pay، وغيرها)
            'automatic_payment_methods' => ['enabled' => true],
            // 📝 معلومات إضافية مخصصة ضمن metadata
            'metadata' => [
                'order_id' => $orderId ?? uniqid(), // رقم الطلب إذا لم يُمرر يتم إنشاؤه تلقائيًا
                'product' => 'Man On The Sun - Book', // وصف المنتج
            ],
        ]);
        // 🔁 إرجاع client_secret المطلوب من الواجهة الأمامية لإتمام عملية الدفع
        return $intent->client_secret;
    } catch (\Stripe\Exception\CardException $e) {
        // ❌ تم رفض البطاقة (مثلاً: لا يوجد رصيد كافٍ أو معلومات غير صحيحة)
        throw new \Exception($e->getDeclineCode() ?? 'تم رفض البطاقة');
    } catch (\Stripe\Exception\ApiErrorException $e) {
        // ❌ أي خطأ عام من واجهة Stripe API (شبكة - باراميتر غير صالح - إلخ)
        throw new \Exception('Stripe API error: ' . $e->getMessage());
    }
}هنا سأقوم بحذف amount من paymentIntent سواء من خلال الدالة createPaymentIntent او createPaymentIntentWithPackage
$response = Http::withHeaders($headers)
    ->asForm()
    ->post('https://api.stripe.com/v1/payment_intents', [
        'currency' => $currency,
        'automatic_payment_methods[enabled]' => 'true', // ✅ هنا النص "true" وليس القيمة المنطقية
        'metadata[order_id]' => $orderId ?? uniqid('book_'),
        'metadata[product]' => 'Man On The Sun',
    ]);في ملف  blade سوف أحصل على الشكل التالي، مع الخطأ بالتحديد
ملف blade
resources/views/payment.blade.php
وفي ملف blade ما يهمنا هو javascript
💻 واجهة الدفع: Stripe Elements (Frontend)
@if (!isset($error) || !$error)
    <script>
        const stripe = Stripe('{{ $publishable_key ?? '' }}');
        const elements = stripe.elements();
        const style = {
            base: {
                fontSize: '16px',
                color: '#424770',
                '::placeholder': { color: '#aab7c4' },
            },
            invalid: { color: '#9e2146' },
        };
        const card = elements.create('card', { style });
        card.mount('#card-element');
        card.on('change', ({ error }) => {
            document.getElementById('card-errors').textContent = error ? error.message : '';
        });
        const form = document.getElementById('payment-form');
        const submitButton = document.getElementById('submit-button');
        const buttonText = document.getElementById('button-text');
        const spinner = document.getElementById('spinner');
        form.addEventListener('submit', async (event) => {
            event.preventDefault();
            submitButton.disabled = true;
            buttonText.textContent = 'Processing...';
            spinner.classList.remove('hidden');
            try {
                const { error, paymentIntent } = await stripe.confirmCardPayment('{{ $client_secret ?? '' }}', {
                    payment_method: {
                        card: card,
                    }
                });
                if (error) {
                    document.getElementById('card-errors').textContent = error.message;
                    submitButton.disabled = false;
                    buttonText.textContent = 'Pay ${{ number_format($amount ?? 50.00, 2) }}';
                    spinner.classList.add('hidden');
                } else {
                    window.location.href = '/payment/success?payment_intent=' + paymentIntent.id;
                }
            } catch (err) {
                document.getElementById('card-errors').textContent = 'An unexpected error occurred. Please try again.';
                submitButton.disabled = false;
                buttonText.textContent = 'Pay ${{ number_format($amount ?? 50.00, 2) }}';
                spinner.classList.add('hidden');
            }
        });
    </script>
@endif| العنصر | HTTP Client | Stripe SDK (مُوصى به) | 
|---|---|---|
| سهولة الاستخدام | ❌ معقد نسبيًا | ✅ بسيط جدًا | 
| التعامل مع الأخطاء | ❌ يدوي | ✅ تلقائي ومدعوم | 
| دعم الميزات المتقدمة | ❌ محدود | ✅ كامل | 
| الصيانة والتحديثات | ❌ غير مضمون | ✅ رسمي ومدعوم من Stripe | 
| الأداء والكفاءة | ❌ عرضة للأخطاء | ✅ محسّن وآمن | 
في هذا الدرس:
## 🎓 الدرس المستفاد
يمكنك إستعراض وتنزيل المشروع عبر GitHub عبر الرابط التالي: