> ## Documentation Index
> Fetch the complete documentation index at: https://shinydapps-bd9fa40b.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# دليل الإنتاج

> نشر واجهات برمجة التطبيقات l402-kit في بيئة الإنتاج — الأداء والموثوقية والتوسع.

## قائمة التحقق قبل الإطلاق

راجع هذه القائمة قبل الانتقال إلى بيئة الإنتاج. كل بند يحيل إلى القسم المقابل أدناه.

<Steps>
  <Step title="تكوين حماية إعادة التشغيل للنسخ المتعددة">
    المحوّل الافتراضي يعمل في الذاكرة فقط. إذا كنت تشغّل أكثر من عملية واحدة (عمال Gunicorn، PM2 cluster، Kubernetes)، فاضبط `SUPABASE_URL` + `SUPABASE_ANON_KEY` أو استخدم `RedisReplayAdapter`. [التفاصيل →](#critical-replay-protection-in-production)
  </Step>

  <Step title="متغيرات البيئة في مدير الأسرار">
    لا تُدرج مفاتيح API مباشرةً في الكود. استخدم `.env` محلياً ومدير الأسرار في الإنتاج. [التفاصيل →](#environment-variables)
  </Step>

  <Step title="وجود نقطة نهاية للتحقق من الصحة">
    يجب ألا تُطلق عمليات مراقبة النظام إنشاء فواتير. أضف مسار `/health` قبل مساراتك المدفوعة. [التفاصيل →](#health-check-endpoint)
  </Step>

  <Step title="استجابات الأخطاء لا تكشف التفاصيل الداخلية">
    التقط أخطاء المزود وأعد استجابة 503 نظيفة — لا تعيد تتبع المكدس. [التفاصيل →](#error-handling)
  </Step>

  <Step title="السعر مقصود">
    يجب أن يعكس `priceSats` قيمة حقيقية. بسعر 1 sat ≈ $0.0006، فإن 100 sats ≈ $0.06 لنقطة نهاية مميزة أمر معقول. لا تضبطه على 0 بالخطأ.
  </Step>

  <Step title="مراقبة وقت التشغيل على نقطة نهاية الفاتورة">
    راقب استجابة 402 (وهي استجابة طبيعية وليست خطأً). تدعم أدوات مثل UptimeRobot توقعات رموز الحالة المخصصة.
  </Step>

  <Step title="تحديد معدل إنشاء الفواتير">
    كل طلب غير مصادق عليه يُنشئ فاتورة Lightning. بدون تحديد المعدل، يمكن لمهاجم استنفاد حصة الفواتير لدى مزودك مجاناً. أضف `express-rate-limit` قبل النشر العلني. [التفاصيل →](#rate-limiting)
  </Step>
</Steps>

***

## هام: حماية إعادة التشغيل في الإنتاج

<Warning>
  المحوّل الافتراضي لحماية إعادة التشغيل **يعمل في الذاكرة فقط** — يُعاد تعيينه عند كل إعادة تشغيل للعملية ولا يعمل عبر نسخ متعددة من الخادم. في بيئة الإنتاج مع أكثر من عملية واحدة (عمال Gunicorn، حاويات Kubernetes، PM2 cluster)، يمكن قبول نفس preimage مرتين.

  **الحل:** اضبط `SUPABASE_URL` + `SUPABASE_ANON_KEY` في بيئتك. سيستخدم الوسيط تلقائياً Supabase كمخزن لإعادة التشغيل، وهو مشترك عبر جميع النسخ.

  لـ Redis: مرر `RedisReplayAdapter` صراحةً (راجع [TypeScript SDK](/sdk/typescript#replay-protection) أو [Python SDK](/sdk/python#replay-protection)).
</Warning>

***

## متغيرات البيئة

لا تُدرج المفاتيح مباشرةً في الكود. استخدم دائماً متغيرات البيئة:

```bash theme={null}
# .env (never commit this)
BLINK_API_KEY=blink_xxx
BLINK_WALLET_ID=your-wallet-id
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=sb_publishable_xxx    # safe to expose to clients
SUPABASE_SERVICE_KEY=sb_secret_xxx      # server-side only — never expose to clients
```

```typescript theme={null}
import 'dotenv/config';
import { BlinkProvider } from 'l402-kit';

const blink = new BlinkProvider(
  process.env.BLINK_API_KEY!,
  process.env.BLINK_WALLET_ID!,
);
```

***

## تسجيل المدفوعات (Supabase)

سجّل كل دفعة في Supabase للوحة تحكم إضافة VS Code والتحليلات:

```typescript theme={null}
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!,
);

app.use(async (req, res, next) => {
  const original = res.json.bind(res);
  res.json = (body) => {
    // Log successful L402 payments (after middleware passes)
    if (req.l402Paid && req.l402Preimage) {
      supabase.from('payments').insert({
        endpoint: req.path,
        preimage: req.l402Preimage,
        amount_sats: req.l402AmountSats,
      }).then(() => {});
    }
    return original(body);
  };
  next();
});
```

***

## النشر على Cloudflare Workers

يعمل l402-kit API على Cloudflare Workers (عوازل V8، بدون تأخير في البداية). يُعاد تعيين مخزن إعادة التشغيل في الذاكرة مع كل عازل — لواجهات برمجة التطبيقات ذات الحركة العالية، استخدم Cloudflare KV أو Durable Objects لحماية إعادة التشغيل.

```typescript theme={null}
// api/data.ts
import { l402 } from 'l402-kit';
import { BlinkProvider } from 'l402-kit';

const blink = new BlinkProvider(
  process.env.BLINK_API_KEY!,
  process.env.BLINK_WALLET_ID!,
);

export default async function handler(req, res) {
  await new Promise<void>((resolve, reject) => {
    l402({ priceSats: 10, lightning: blink })(req, res, (err) => {
      if (err) reject(err); else resolve();
    });
  });
  res.json({ data: 'premium content' });
}
```

***

## Docker

```dockerfile theme={null}
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY dist ./dist
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/server.js"]
```

```yaml theme={null}
# docker-compose.yml
services:
  api:
    build: .
    environment:
      BLINK_API_KEY: ${BLINK_API_KEY}
      BLINK_WALLET_ID: ${BLINK_WALLET_ID}
    ports:
      - "3000:3000"
```

***

## حذف بيانات المستخدم

افتح نقطة نهاية للحذف تتيح للمستخدمين إزالة بياناتهم بشكل دائم. تتضمن واجهة l402-kit الخلفية `/api/delete-data` جاهزةً للاستخدام:

```http theme={null}
POST /api/delete-data
Content-Type: application/json

{ "lightningAddress": "user@blink.sv" }
```

```json theme={null}
// 200 OK
{ "deleted": { "payments": 42, "proAccess": true } }
```

تعرض إضافة VS Code هذه الميزة كلوحة **Danger Zone** في أسفل لوحة التحكم — يجب على المستخدمين كتابة عنوان Lightning الخاص بهم للتأكيد، ثم تُمحى جميع سجلات المدفوعات وصلاحية Pro. تستخدم العملية مفتاح خدمة Supabase من جهة الخادم؛ لا يملك مفتاح anon صلاحية DELETE.

***

## معالجة الأخطاء

التقط أخطاء المزود قبل أن تظهر كاستجابات 500 غير منسقة:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { l402, L402Error } from 'l402-kit';

  app.get('/api/data', l402({ priceSats: 10, lightning }), async (req, res) => {
    try {
      res.json({ data: await fetchData() });
    } catch (err) {
      if (err instanceof L402Error) {
        // Token invalid, expired, or already used — let the middleware handle it
        return res.status(err.status).json({ error: err.code });
      }
      console.error('[api/data]', err);
      res.status(503).json({ error: 'Service temporarily unavailable' });
    }
  });
  ```

  ```python Python theme={null}
  from l402kit import l402_required, L402Error
  from fastapi import Request
  from fastapi.responses import JSONResponse

  @app.exception_handler(L402Error)
  async def l402_exception_handler(request: Request, exc: L402Error):
      return JSONResponse(status_code=exc.status_code, content={"error": exc.code})

  @app.exception_handler(Exception)
  async def generic_exception_handler(request: Request, exc: Exception):
      return JSONResponse(status_code=503, content={"error": "Service temporarily unavailable"})
  ```
</CodeGroup>

**قواعد عامة:**

* لا تُعد تتبعات المكدس إلى العملاء أبداً — سجّلها من جهة الخادم.
* انتهاء مهلة المزود (503) مؤقت — يمكن إعادة المحاولة بتراجع تدريجي.
* أخطاء الرمز (401) ليست مؤقتة أبداً — لا تُعد المحاولة تلقائياً، اطلب فاتورة جديدة.
* أخطاء تحديد المعدل (429) — أظهر حقل `retryAfter` للمُستدعي.

راجع [مرجع الأخطاء](/reference/errors) للاطلاع على القائمة الكاملة لرموز الأخطاء المنظمة.

***

## تحديد المعدل

كل طلب غير مصادق عليه يُطلق `createInvoice()` على مزود Lightning الخاص بك. بدون تحديد المعدل، يمكن لأي شخص إغراق نقطة النهاية الخاصة بك واستنفاد حصة API لدى مزودك مجاناً — حتى دون دفع sat واحد.

أضف [`express-rate-limit`](https://www.npmjs.com/package/express-rate-limit) قبل مسارات L402 الخاصة بك:

```bash theme={null}
npm install express-rate-limit
```

```typescript theme={null}
import rateLimit from "express-rate-limit";
import { l402 } from "l402-kit";

// Limit invoice creation: 30 unauthenticated requests/minute per IP
const invoiceLimit = rateLimit({
  windowMs: 60_000,
  max: 30,
  // Only apply to requests that don't already carry a valid L402 header
  skip: (req) => req.headers.authorization?.startsWith("L402 ") ?? false,
  message: { error: "Too many requests — slow down" },
});

app.get("/api/premium", invoiceLimit, l402({ priceSats: 10, lightning }), handler);
```

<Note>
  يتم تخطي العملاء الذين يدفعون بالفعل عبر دالة `skip` — يُطبَّق الحد فقط على الطلبات غير المصادق عليها التي تُطلق إنشاء فاتورة جديدة. لا يُقيَّد الدافعون الشرعيون أبداً.
</Note>

لـ FastAPI:

```python theme={null}
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.get("/api/premium")
@limiter.limit("30/minute")
@l402_required(price_sats=10, lightning=lightning)
async def premium(request: Request):
    return {"data": "paid content"}
```

***

## نقطة نهاية التحقق من الصحة

أضف دائماً فحص صحة مجاني حتى لا تُطلق أدوات المراقبة إنشاء فواتير:

```typescript theme={null}
app.get('/health', (req, res) => res.json({ ok: true, ts: Date.now() }));

// Paid endpoint
app.get('/api/data', l402({ priceSats: 10, lightning: blink }), handler);
```

***

## المراقبة

المقاييس الرئيسية للتتبع:

* **معدل استجابة 402** — المعدل الأساسي الصحي مرتفع (معظم المُستدعين بحاجة إلى الدفع)
* **معدل التحقق من المدفوعات** — نسبة المكالمات المدفوعة مقارنةً بغير المدفوعة
* **زمن استجابة المزود** — يجب أن يكون `blink.createInvoice()` \< 500ms
* **محاولات إعادة التشغيل** — الارتفاع المفاجئ يشير إلى هجمات إعادة استخدام الرمز

```typescript theme={null}
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    console.log(JSON.stringify({
      method: req.method,
      path: req.path,
      status: res.statusCode,
      ms: Date.now() - start,
      paid: res.statusCode !== 402,
    }));
  });
  next();
});
```

***

## الأداء

* التحقق من الرمز هو **O(1)** — تشفير بحت، بدون قاعدة بيانات، بدون شبكة
* إنشاء الفاتورة (مسار 402) يستدعي واجهة برمجة تطبيقات مزود Lightning الخاص بك — أضف ذاكرة تخزين مؤقت إذا كنت تتوقع ضرب نفس نقطة النهاية بشكل متكرر قبل الدفع
* مخزن إعادة التشغيل هو `Set` في الذاكرة — لعمليات النشر متعددة النسخ، استبدله بـ Redis

```typescript theme={null}
// Redis replay store example
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);

// Pass custom replay function to middleware
app.get('/api', l402({
  priceSats: 10,
  lightning: blink,
  checkReplay: async (preimage) => {
    const set = await redis.set(`l402:${preimage}`, '1', 'NX', 'EX', 86400);
    return set === 'OK';
  },
}), handler);
```
