پیاده سازی سیستم پرداخت در Node.js
پرداخت آنلاین بخش حیاتی هر کسبوکار دیجیتال است. در این مقاله بهصورت عملی و فنی توضیح میدهم چگونه یک سیستم پرداخت امن و قابلاعتماد با Node.js طراحی و پیادهسازی کنید. مباحث شامل انتخاب درگاه، معماری Backend، امنسازی، وبهوکها، پرداختهای تکرارشونده و نکات تست و خطایابی است.
معماری کلی و الگوهای مرسوم
معماری پایه معمولاً شامل این اجزاست:
- کلاینت (وب یا موبایل) — ایجاد سفارش و آغاز فرایند پرداخت
- سرور Node.js — ایجاد پرداخت در درگاه، نگهداری وضعیت سفارش، پردازش وبهوک
- درگاه پرداخت — پردازش تراکنش، تأیید و ارسال رویداد
- بانک یا شبکه بانکی — نهاییسازی تراکنش
دو الگوی رایج برای پرداخت:
- Redirect (کاربر به صفحه درگاه هدایت میشود). ساده و مناسب برای درگاههای داخلی مانند زرینپال، آیدیپی.
- Tokenization / Client-side SDK (مثلاً Stripe Elements) — اطلاعات کارت هرگز به سرور شما ارسال نمیشود و سطح امنیت بالاتری فراهم میشود.
امنیت و نکات حساس
- همیشه از HTTPS استفاده کنید.
- اطلاعات کارت را هرگز در سرور نگهداری نکنید مگر اینکه با PCI DSS سازگار باشید.
- از توکنها و توکنسازی (tokenization) استفاده کنید.
- وبهوکها را با اعتبارسنجی امضا بررسی کنید.
- برای جلوگیری از پرداختهای تکراری از idempotency key استفاده کنید.
نمونه: ایجاد Payment Intent با Stripe در Node.js
// server.js
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.use(express.json());
app.post('/create-payment-intent', async (req, res) => {
const { amount, currency } = req.body;
try {
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
payment_method_types: ['card'],
});
res.send({ clientSecret: paymentIntent.client_secret });
} catch (err) {
res.status(500).send({ error: err.message });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));توضیح: این کد یک API ساده با Express ایجاد میکند که یک PaymentIntent در Stripe میسازد و client_secret را برمیگرداند. کلاینت با استفاده از Stripe.js و client_secret تراکنش را کامل میکند. این روش کارت را از سرور شما دور نگه میدارد و سازگار با PCI است.
بررسی وبهوک و اعتبارسنجی امضا (Stripe)
// webhook.js
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const bodyParser = require('body-parser');
const app = express();
// Stripe requires the raw body to construct the event
app.post('/webhook', bodyParser.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
if (event.type === 'payment_intent.succeeded') {
const paymentIntent = event.data.object;
// Update order status in DB, send receipt, etc.
}
res.json({ received: true });
});
app.listen(3001);توضیح: وبهوکهای Stripe باید با استفاده از secret مخصوص وبهوک اعتبارسنجی شوند. این کد از bodyParser.raw استفاده میکند چون Stripe برای ساخت event به بدنه خام نیاز دارد. پس از اعتباریابی، میتوانید وضعیت سفارش را بروز کنید یا ایمیل ارسال کنید.
استفاده از idempotency برای جلوگیری از پرداخت دوباره
// idempotent example with Stripe
const idempotencyKey = require('crypto').randomBytes(16).toString('hex');
await stripe.paymentIntents.create({
amount: 5000,
currency: 'usd',
payment_method_types: ['card'],
}, {
idempotencyKey
});توضیح: با ارسال idempotencyKey در هر درخواست به درگاه، در صورت تکرار ناخواسته درخواست (مثلاً بهخاطر خطای شبکه)، درگاه آن را بهعنوان همان تراکنش قبلی شناسایی میکند و از دوبارهپرداخت جلوگیری میشود.
نمونه ساده برای درگاههای Redirect (مثلاً زرینپال یا آیدیپی)
// redirect example
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
app.post('/create-invoice', async (req, res) => {
const { amount, callbackUrl } = req.body;
// Call gateway API and get payment URL
const response = await fetch('https://api.example-gateway.com/v1/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'API-Key': process.env.GATEWAY_KEY },
body: JSON.stringify({ amount, callbackUrl })
});
const data = await response.json();
res.send({ paymentUrl: data.url });
});
app.listen(3002);توضیح: الگوی redirect اغلب ساده است: سرور از درگاه درخواست میکند و URL پرداخت را دریافت میکند، سپس کاربر را به آن هدایت میکنید. پس از پرداخت، درگاه کاربر را به callbackUrl شما بازمیگرداند و شما وضعیت پرداخت را بررسی و ذخیره میکنید.
پرداختهای تکرارشونده (Subscriptions)
برای اشتراکها بهتر است از سیستم توکنسازی درگاه (مثل Stripe Billing) استفاده کنید. جریان کلی:
- ایجاد مشتری (Customer) و ذخیره توکن پرداخت
- ساخت Subscription با Plan یا قیمت مشخص
- مدیریت نوتیفیکیشنها از طریق وبهوک برای پرداخت ناموفق، پایان اشتراک، تمدید خودکار
تست، sandbox و خطایابی
- همیشه از حساب sandbox یا تست درگاه استفاده کنید.
- وبهوکها را با ابزارهایی مثل ngrok به لوکال هدایت و تست کنید.
- سناریوهای خرابی (رد کارت، خطای شبکه، پرداخت ناقص) را شبیهسازی کنید.
- لاگهای کافی و قابل ردیابی بنویسید؛ تراکنشها را با شناسههای یکتا نشانهگذاری کنید.
مقایسه سریع درگاههای محبوب
| درگاه | پرداختهای بینالمللی | پرداخت تکراری | محیط تست |
|---|---|---|---|
| Stripe | قوی | بلی (Billing) | بلی |
| PayPal | قوی | بلی | بلی |
| زرینپال (ایران) | خیر | وابسته به سرویس | بلی |
| آیدیپی (IDPay) | خیر | بلی/خیر بستگی به پلن | بلی |
نکات عملی و توصیههای تخصصی
- معماری میکروسرویس: اگر پرداخت بخش مهمی از کسبوکار است، آن را به سرویس جداگانه تبدیل کنید تا ایزوله و مقیاسپذیر باشد.
- سیاست retry برای وبهوکها و قرار دادن صف برای پردازش غیرهمزمان جهت تحمل خطا.
- متمرکز کردن لاگها و مانیتورینگ تراکنشها برای شناسایی الگوهای تقلب یا خطا.
- آموزش تیم پشتیبانی برای مراحل بازیابی پرداخت، بازگشت وجه و پیگیری تراکنشها.
پیادهسازی پرداخت در Node.js ترکیبی از انتخاب درگاه مناسب، رعایت امنیت، طراحی منطقی API و مدیریت رویدادها (وبهوک) است. با رعایت اصول بالا و استفاده از امکانات مدرن درگاهها مثل توکنها، وبهوکهای امضاشده و idempotency میتوانید سیستمی امن و قابلاعتماد بسازید.
آیا این مطلب برای شما مفید بود ؟




