To'lov tizimlari — Click / Payme / Stripe
Muammodan boshlaylik
Siz e-commerce ilova yozdingiz. Foydalanuvchi mahsulot tanladi, "To'lash" tugmasini bosdi. Endi nima bo'ladi?
Pul to'g'ridan-to'g'ri sizning serveringizga kelmaydi. O'rtada to'lov tizimi turadi:
Foydalanuvchi → Click/Payme/Stripe → Sizning server
↓
Bank karta tekshiruvi
Pul o'tkazish
Sizga xabar berish
To'lov tizimi:
- Karta ma'lumotlarini xavfsiz saqlaydi (siz saqlamaysiz)
- Bank bilan aloqa qiladi
- Pul o'tganini sizga bildiradi
O'zbekistondagi to'lov tizimlari
Click va Payme — O'zbekistondagi eng mashhur to'lov tizimlari.
Ikkalasi ham xuddi shunday ishlaydi:
- Siz buyurtma yaratasiz (amount, orderId)
- Foydalanuvchi ularning sahifasiga o'tadi, to'laydi
- Ular sizning serveringizga Prepare va Complete so'rovlar yuboradi
- Siz tasdiqlaysiz — pul hisobingizga o'tadi
Payme integratsiyasi
Merchant API ni tushunish
Payme 2 ta so'rov yuboradi:
CheckPerformTransaction → Buyurtma to'lanishi mumkinmi?
CreateTransaction → Tranzaksiya yaratildi
PerformTransaction → To'lov muvaffaqiyatli, pul o'tkazildi
CancelTransaction → Bekor qilindi
O'rnatish
npm install @nestjs/axios axios
Controller
// payment/payme.controller.ts
@Controller('payment/payme')
export class PaymeController {
constructor(private paymeService: PaymeService) {}
@Post()
async handle(@Body() body: any, @Headers('authorization') auth: string) {
// Autentifikatsiya tekshiruvi
if (!this.paymeService.checkAuth(auth)) {
return { error: { code: -32504, message: 'Insufficient privilege' } };
}
const { method, params, id } = body;
switch (method) {
case 'CheckPerformTransaction':
return this.paymeService.checkPerform(params, id);
case 'CreateTransaction':
return this.paymeService.createTransaction(params, id);
case 'PerformTransaction':
return this.paymeService.performTransaction(params, id);
case 'CancelTransaction':
return this.paymeService.cancelTransaction(params, id);
default:
return { error: { code: -32601, message: 'Method not found' }, id };
}
}
}
Service
// payment/payme.service.ts
@Injectable()
export class PaymeService {
checkAuth(authHeader: string): boolean {
// Basic auth: "Basic base64(login:password)"
const expected = Buffer.from(
`Paycom:${process.env.PAYME_KEY}`
).toString('base64');
return authHeader === `Basic ${expected}`;
}
async checkPerform(params: any, id: string) {
const { amount, account } = params;
const orderId = account.order_id;
const order = await this.ordersService.findOne(orderId);
if (!order) {
return {
result: null,
error: { code: -31050, message: 'Order not found' },
id,
};
}
// Summa mos kelishini tekshirish (tiyin da)
if (order.amount * 100 !== amount) {
return {
result: null,
error: { code: -31001, message: 'Wrong amount' },
id,
};
}
return { result: { allow: true }, id };
}
async performTransaction(params: any, id: string) {
const { id: transactionId, account } = params;
const orderId = account.order_id;
// Buyurtmani to'langan deb belgilash
await this.ordersService.markAsPaid(orderId, transactionId);
return {
result: {
transaction: transactionId,
perform_time: Date.now(),
state: 2, // to'langan
},
id,
};
}
}
Click integratsiyasi
Click ikki endpoint bilan ishlaydi:
// payment/click.controller.ts
@Controller('payment/click')
export class ClickController {
// 1. Prepare — to'lov boshlanishidan oldin tekshirish
@Post('prepare')
async prepare(@Body() body: ClickPrepareDto) {
const { merchant_trans_id, amount, sign_time, sign_string } = body;
// Imzoni tekshirish
const expectedSign = this.clickService.generateSign(body);
if (sign_string !== expectedSign) {
return { error: -1, error_note: 'Sign check failed!' };
}
const order = await this.ordersService.findOne(merchant_trans_id);
if (!order) {
return { error: -5, error_note: 'Order not found' };
}
return {
click_trans_id: body.click_trans_id,
merchant_trans_id,
merchant_prepare_id: order.id,
error: 0,
error_note: 'Success',
};
}
// 2. Complete — to'lov tugagandan keyin
@Post('complete')
async complete(@Body() body: ClickCompleteDto) {
const { merchant_trans_id, error } = body;
if (error < 0) {
// To'lov bekor qilindi
await this.ordersService.markAsCancelled(merchant_trans_id);
return { error: 0, error_note: 'Cancelled' };
}
// Muvaffaqiyatli to'lov
await this.ordersService.markAsPaid(merchant_trans_id);
return { error: 0, error_note: 'Success' };
}
}
Stripe integratsiyasi (xalqaro)
Stripe — xalqaro to'lovlar uchun. AQSh, Yevropa bozori.
npm install stripe
// payment/stripe.service.ts
import Stripe from 'stripe';
@Injectable()
export class StripeService {
private stripe: Stripe;
constructor() {
this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
}
// To'lov sahifasi URL yaratish
async createCheckoutSession(orderId: string, amount: number) {
const session = await this.stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [{
price_data: {
currency: 'usd',
product_data: { name: `Order #${orderId}` },
unit_amount: amount * 100, // cent da
},
quantity: 1,
}],
mode: 'payment',
success_url: `${process.env.FRONTEND_URL}/payment/success?orderId=${orderId}`,
cancel_url: `${process.env.FRONTEND_URL}/payment/cancel`,
});
return { url: session.url }; // ← foydalanuvchini shu URLga yo'naltiring
}
}
// payment/stripe-webhook.controller.ts
@Controller('payment/stripe/webhook')
export class StripeWebhookController {
@Post()
async webhook(
@Req() req: RawBodyRequest<Request>,
@Headers('stripe-signature') sig: string,
) {
let event: Stripe.Event;
try {
event = this.stripe.webhooks.constructEvent(
req.rawBody,
sig,
process.env.STRIPE_WEBHOOK_SECRET,
);
} catch {
throw new BadRequestException('Webhook signature error');
}
if (event.type === 'checkout.session.completed') {
const session = event.data.object as Stripe.Checkout.Session;
const orderId = session.metadata.orderId;
await this.ordersService.markAsPaid(orderId);
}
}
}
⚠️ Stripe webhook uchun raw body kerak.
main.tsda:app.use('/payment/stripe/webhook', express.raw({ type: 'application/json' }))
To'lov oqimi — umumiy
1. Foydalanuvchi "To'lash" tugmasini bosdi
→ POST /orders → order yaratildi (status: PENDING)
2. To'lov sahifasiga yo'naltirildi
→ Click/Payme/Stripe sahifasi
3. Foydalanuvchi to'ladi
→ To'lov tizimi sizning serverga webhook yubordi
4. Siz tasdiqlading
→ order.status = PAID
→ Email/Telegram xabar
5. Foydalanuvchi success sahifaga qaytdi
Tez-tez uchraydigan xatolar
1. Sign mismatch
error: -1, error_note: "Sign check failed"
Sababi: Imzoni hisoblashda maydonlar tartibi yoki kalit noto'g'ri. Yechim: Dokumentatsiyadan imzo formulasini qayta tekshiring.
2. Webhook ikki marta keldi
Sababi: To'lov tizimlari xato bo'lsa qayta yuboradi.
Yechim: transactionId ga unique constraint qo'ying — ikki marta qabul qilinmasin.
3. Test rejimida ishlayapti, prod da yo'q
Sababi: Test API key va prod API key boshqacha.
Yechim: .env.production da prod keylarni sozlang.
Xulosa
Eslab qoling:
- To'lov tizimlari webhook yuboradi — siz eshitasiz
- Payme: CheckPerform → CreateTransaction → PerformTransaction
- Click: Prepare → Complete
- Stripe: Checkout Session → Webhook
- Webhook xabarlar takrorlanishi mumkin — idempotent qiling
- Hech qachon karta ma'lumotlarini o'zingiz saqlamang
Keyingi dars: 52-dars: Swagger — API dokumentatsiya