كيفية بناء نظام دفع USDT آلي داخل Telegram Bot باستخدام Go

تعرف على كيفية بناء نظام دفع USDT آلي وغير احتجازي داخل Telegram Bot باستخدام Go، مع إنشاء الفواتير، التحقق من Webhooks، معالجة المدفوعات، وتأمين النظام ضد التكرار والاحتيال.
مقدمة
إذا كنت تبيع اشتراكات رقمية أو ملفات أو خدمات عبر Telegram، فمن المحتمل أنك بدأت بطريقة بسيطة: ترسل عنوان USDT للمستخدم، تنتظر التحويل، ثم تطلب منه إرسال لقطة شاشة أو Transaction Hash للتحقق من الدفع.
قد تنجح هذه الطريقة مع أول عدة عملاء، لكنها تتحول بسرعة إلى عبء تشغيلي كبير مع زيادة عدد الطلبات. ستجد نفسك تقضي وقتاً أطول في التحقق من المعاملات عبر مستكشفات البلوكتشين بدلاً من تطوير المنتج نفسه.
في هذا الدليل العملي سنتعلم كيفية بناء نظام دفع آلي وغير احتجازي (Non-Custodial) يسمح للمستخدم بالدفع باستخدام USDT بينما تذهب الأموال مباشرة إلى محفظتك، ويتولى النظام التحقق من المعاملة وتسليم المنتج تلقائياً.
ما الذي سنبنيه؟
بنهاية هذا الدليل سيكون لدينا النظام التالي:
- Telegram Bot يستقبل طلب الشراء.
- خدمة Backend مكتوبة بلغة Go.
- قاعدة بيانات PostgreSQL لإدارة الطلبات.
- مزود دفع Crypto يدعم Webhooks.
- نظام تحقق آمن من المدفوعات.
- Queue لمعالجة عمليات التسليم.
- تسليم تلقائي للمنتج أو الاشتراك.
المتطلبات الأساسية
قبل البدء تأكد من توفر ما يلي:
البرمجيات
- Go 1.24 أو أحدث
- PostgreSQL 16+
- Docker - اختياري
- Telegram Bot Token
الحسابات
- حساب Telegram Bot
- محفظة USDT
- حساب لدى مزود الدفع
- VPS أو Cloud Server
المعرفة المطلوبة
- أساسيات Go
- REST APIs
- PostgreSQL
- Webhooks
مخطط البنية المعمارية
شرح المكونات
Telegram Bot
واجهة المستخدم الرئيسية.
Go Backend
العقل المركزي للنظام.
PostgreSQL
تخزين الطلبات وحالات الدفع.
Payment Provider
إنشاء الفواتير ومراقبة الشبكة.
Fulfillment Queue
ضمان عدم تسليم المنتج أكثر من مرة.
الخطوة الأولى: إنشاء مشروع Go
أنشئ مجلد المشروع:
mkdir telegram-payment-bot
cd telegram-payment-bot
go mod init telegram-payment-bot
قم بتثبيت الحزم الأساسية:
go get github.com/gin-gonic/gin
go get github.com/jackc/pgx/v5
go get github.com/go-telegram-bot-api/telegram-bot-api/v5
هيكل المشروع المقترح:
cmd/
internal/
├── api/
├── bot/
├── payment/
├── webhook/
├── database/
├── worker/
└── models/
الخطوة الثانية: إنشاء جدول الطلبات
سنبدأ بإنشاء جدول الطلبات.
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id BIGINT NOT NULL,
product_id VARCHAR(50),
amount_usd NUMERIC(12,2),
invoice_id VARCHAR(255),
status VARCHAR(50),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
لماذا لا نستخدم is_paid؟
خطأ شائع جداً:
is_paid BOOLEAN
هذا الحقل لا يكفي.
في الواقع توجد حالات كثيرة:
- Awaiting Payment
- Paid
- Underpaid
- Expired
- Refunded
- Fulfilled
لذلك سنستخدم State Machine.
آلة الحالات المقترحة
الخطوة الثالثة: إنشاء الطلب محلياً
قبل إنشاء الفاتورة لدى مزود الدفع يجب إنشاء الطلب داخل قاعدة البيانات.
order := Order{
ID: uuid.New(),
UserID: telegramUserID,
AmountUSD: 29.00,
Status: "awaiting_payment",
}
err := repository.CreateOrder(order)
لماذا؟
إذا فشل الاتصال بمزود الدفع يمكن إعادة المحاولة دون فقدان البيانات.
الخطوة الرابعة: إنشاء فاتورة دفع
بعد إنشاء الطلب محلياً نرسل طلباً إلى مزود الدفع.
type CreateInvoiceRequest struct {
Amount float64 `json:"amount"`
Currency string `json:"currency"`
Description string `json:"description"`
}
إرسال الطلب:
resp, err := client.CreateInvoice(
order.ID,
order.AmountUSD,
)
بعد النجاح:
order.InvoiceID = resp.InvoiceID
repository.UpdateInvoice(
order.ID,
resp.InvoiceID,
)
الخطوة الخامسة: إرسال رابط الدفع للمستخدم
بعد إنشاء الفاتورة:
msg := tgbotapi.NewMessage(
chatID,
fmt.Sprintf(
"قم بالدفع من خلال الرابط التالي:\n%s",
invoiceURL,
),
)
bot.Send(msg)
الآن ينتظر المستخدم الدفع بينما يراقب مزود الدفع الشبكة.
ماذا يحدث بعد الدفع؟
هنا تأتي أهمية الـ Webhooks.
بمجرد اكتشاف التحويل يقوم مزود الدفع بإرسال إشعار إلى خادمنا يحتوي على:
- Invoice ID
- Amount
- Network
- Transaction Hash
- Payment Status
لكن لا يجب الثقة بهذه البيانات مباشرة.
في القسم التالي سنقوم ببناء Webhook Handler آمن والتحقق من HMAC Signature خطوة بخطوة.
الخطوة السادسة: إنشاء Webhook Handler آمن
بعد أن يرسل المستخدم الدفع، سيقوم مزود الدفع بإرسال Webhook إلى نقطة النهاية الخاصة بك.
مثال:
POST /webhooks/payment
Content-Type: application/json
X-Recv-Signature: abc123...
X-Recv-Timestamp: 1759842842
أحد أكبر الأخطاء التي يرتكبها المطورون هو الثقة مباشرة في البيانات القادمة من الـ Webhook.
لا تفعل ذلك أبداً.
يجب التحقق أولاً من:
- صحة التوقيع Signature
- صحة الطابع الزمني Timestamp
- عدم تكرار الحدث Event Replay
- تطابق الفاتورة مع قاعدة البيانات
إنشاء Endpoint
router.POST(
"/webhooks/payment",
webhookHandler,
)
قراءة الطلب الخام
لا تقم بتحليل JSON مباشرة.
body, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(400, gin.H{
"error": "invalid request",
})
return
}
سنحتاج إلى البيانات الخام للتحقق من التوقيع.
الخطوة السابعة: التحقق من HMAC Signature
الفكرة بسيطة:
يقوم مزود الدفع بإنشاء بصمة رقمية باستخدام:
- Secret Key
- Timestamp
- Raw Request Body
ثم يرسلها في Header.
يجب علينا إعادة حساب البصمة ومقارنتها.
إنشاء دالة التحقق
func VerifySignature(
secret string,
timestamp string,
payload []byte,
receivedSignature string,
) bool {
data := timestamp + "." + string(payload)
mac := hmac.New(
sha256.New,
[]byte(secret),
)
mac.Write([]byte(data))
expected := hex.EncodeToString(
mac.Sum(nil),
)
return hmac.Equal(
[]byte(expected),
[]byte(receivedSignature),
)
}
لماذا نستخدم hmac.Equal ؟
خطأ شائع:
if expected == received {
}
هذه المقارنة قد تكشف معلومات للمهاجم من خلال Timing Attacks.
لذلك استخدم:
hmac.Equal()
التي تنفذ مقارنة ثابتة الزمن.
الخطوة الثامنة: منع Replay Attacks
حتى لو كان التوقيع صحيحاً، يمكن للمهاجم إعادة إرسال نفس الطلب.
لذلك يجب التحقق من Timestamp.
timestampInt, _ := strconv.ParseInt(
timestamp,
10,
64,
)
requestTime := time.Unix(
timestampInt,
0,
)
if time.Since(requestTime) >
5*time.Minute {
return errors.New(
"expired webhook",
)
}
أي Webhook أقدم من 5 دقائق يتم رفضه.
الخطوة التاسعة: معالجة الحدث بطريقة Idempotent
في العالم الحقيقي ستتلقى أحياناً نفس Webhook مرتين أو ثلاث مرات.
هذا أمر طبيعي.
السبب أن مزود الدفع يعمل بطريقة:
At-Least-Once Delivery
وليس:
Exactly-Once Delivery
المشكلة
إذا قمت بإرسال المنتج مباشرة:
deliverProduct()
ثم تعطلت الخدمة قبل إعادة:
200 OK
فسيتم إعادة إرسال الـ Webhook.
والنتيجة؟
قد يحصل العميل على المنتج مرتين أو ثلاث مرات.
الحل: Inbox Pattern
أنشئ جدولاً خاصاً بالأحداث.
CREATE TABLE webhook_events (
event_id VARCHAR(255) PRIMARY KEY,
event_type VARCHAR(100),
received_at TIMESTAMP DEFAULT NOW()
);
معالجة الحدث
INSERT INTO webhook_events (
event_id,
event_type
)
VALUES (
$1,
$2
)
ON CONFLICT DO NOTHING;
إذا كان الحدث موجوداً مسبقاً فلن يتم تنفيذه مرة أخرى.
الخطوة العاشرة: تحديث حالة الطلب
بعد نجاح التحقق:
UPDATE orders
SET status='paid'
WHERE invoice_id=$1;
لكن لا تقم بتسليم المنتج هنا.
الخطوة الحادية عشرة: إنشاء Queue للتنفيذ
أنشئ جدول الوظائف.
CREATE TABLE fulfillment_jobs (
id UUID PRIMARY KEY,
order_id UUID,
status VARCHAR(50),
created_at TIMESTAMP DEFAULT NOW()
);
إضافة مهمة جديدة
INSERT INTO fulfillment_jobs (
id,
order_id,
status
)
VALUES (
gen_random_uuid(),
$1,
'pending'
);
الـ Webhook انتهى عمله هنا.
لماذا لا نرسل المنتج مباشرة؟
لأن العمليات التالية قد تفشل:
- Telegram API
- SMTP
- Cloud Storage
- License Generation
لذلك نستخدم Worker منفصل.
الخطوة الثانية عشرة: بناء Worker
عامل التنفيذ يقرأ المهام باستمرار.
for {
jobs := repository.GetPendingJobs()
for _, job := range jobs {
processJob(job)
}
time.Sleep(
5 * time.Second,
)
}
تنفيذ المهمة
func processJob(
job FulfillmentJob,
) {
sendProductToUser(job)
repository.MarkCompleted(
job.ID,
)
}
الخطوة الثالثة عشرة: تسليم المنتج عبر Telegram
مثال إرسال رابط تحميل.
msg := tgbotapi.NewMessage(
chatID,
"شكراً لعملية الشراء.\n\n"+
"رابط التحميل:\n"+
downloadURL,
)
bot.Send(msg)
التعامل مع الحالات الخاصة
في الواقع ليست كل المدفوعات مثالية.
حالة Underpayment
المستخدم أرسل:
29.00 USDT
بينما المطلوب:
29.004281 USDT
لا توافق تلقائياً.
قم بوضع الطلب في:
payment_review
حالة Overpayment
قد يدفع المستخدم أكثر من المطلوب.
يمكنك:
- الموافقة التلقائية
- إضافة رصيد للحساب
- إنشاء Refund
بحسب سياسة مشروعك.
حالة Wrong Network
مثال:
المطلوب:
TRC20
لكن المستخدم دفع عبر:
BEP20
هذه حالة تحتاج مراجعة يدوية.
حالة Expired Invoice
إذا انتهت صلاحية الفاتورة:
UPDATE orders
SET status='expired'
ثم اطلب من المستخدم إنشاء فاتورة جديدة.
أفضل الممارسات الأمنية
لا تخزن الأسرار داخل الكود
خطأ:
const Secret =
"123456789"
الصحيح:
os.Getenv(
"WEBHOOK_SECRET",
)
استخدم HTTPS فقط
لا تستقبل Webhooks عبر HTTP.
سجل جميع الأحداث
logger.Info(
"invoice paid",
zap.String(
"invoice",
invoiceID,
),
)
السجلات مهمة جداً عند التحقيق في المشكلات.
راقب معدل الأخطاء
أنشئ Dashboard تتابع:
- عدد الفواتير
- المدفوعات الناجحة
- المدفوعات الفاشلة
- الأخطاء
- زمن معالجة الـ Webhooks
مخطط دورة العمل الكاملة
sequenceDiagram
User->>Telegram Bot: /buy
Telegram Bot->>Go Backend:
Create Order
Go Backend->>Payment Provider:
Create Invoice
Payment Provider-->>User:
Checkout URL
User->>Blockchain:
Send USDT
Blockchain->>Payment Provider:
Transaction Confirmed
Payment Provider->>Webhook Endpoint:
invoice.paid
Webhook Endpoint->>Database:
Mark Paid
Webhook Endpoint->>Queue:
Create Job
Worker->>Telegram Bot:
Deliver Product
Telegram Bot-->>User:
Download Link
اختبار النظام
قبل الإطلاق تأكد من اختبار:
إنشاء الفاتورة
✅ يعمل
إعادة إرسال Webhook
✅ لا يكرر التنفيذ
انقطاع قاعدة البيانات
✅ لا يفقد الطلبات
فشل Telegram API
✅ يعيد المحاولة
دفع أقل من المطلوب
✅ ينتقل إلى Review
أخطاء شائعة يجب تجنبها
❌ استخدام is_paid فقط
❌ تسليم المنتج داخل Webhook
❌ عدم التحقق من Signature
❌ عدم استخدام Idempotency
❌ تخزين Secrets داخل Git
❌ الاعتماد على لقطات الشاشة كدليل دفع
الخاتمة
بناء نظام دفع USDT آلي داخل Telegram Bot ليس مجرد ربط مزود دفع مع البوت. النظام الحقيقي يجب أن يكون قادراً على التعامل مع التكرار، والأخطاء، ومحاولات الاحتيال، وانقطاع الخدمات دون فقدان الطلبات أو تسليم المنتجات أكثر من مرة.
باستخدام Go وPostgreSQL وWebhooks الموقعة وFulfillment Queue يمكنك بناء بنية دفع احترافية قابلة للتوسع من عشرات الطلبات يومياً إلى آلاف المعاملات دون الحاجة إلى التحقق اليدوي من كل عملية دفع.
الاستثمار في هذه البنية منذ البداية يوفر ساعات طويلة من أعمال الدعم ويمنح المستخدم تجربة شراء سلسة وآمنة، وهو ما يجعل مشروعك أكثر قابلية للنمو على المدى الطويل.
كن أول من يعرف بمستقبل التقنية
أهم الأخبار والتحليلات التقنية مباشرة في بريدك.