Idempotentlik — bu header emas. Bu shartnoma.
Har bir to'lov provayderi webhook'ingiz idempotent bo'lishini talab qiladi. Buning aslida nima talab qilishini deyarli hech kim aytmaydi — bu identifikatsiya haqidagi qaror, holat haqidagi qaror va dalilni qo'yadigan joy.

Menga birinchi marta endpointni idempotent qil deyilganda, ko'pchilik qiladigan ishni qildim: so'rov id'siga unikal indeks qo'ydim, insert'ni try/catch ichiga o'radim va duplicate key xatosini yutdim. Tamom. Idempotent.
Aslida yo'q edi. U to'lov webhook'i takrorlanadigan uchta yo'ldan bittasini hal qilardi, va aynan eng xavfsizini.
Hozir tushunganimni yozib qo'ymoqchiman, chunki "shunchaki idempotent qil" — to'liq eshitiladigan, lekin to'liq bo'lmagan maslahat.
Takrorlanish umuman nega bo'ladi
Idempotentlik — kamdan-kam uchraydigan nosozlikdan himoya qiladigan qo'shimcha yaxshilik emas. Takroriy yetkazib berish — siz integratsiya qilayotgan tizimlarning odatiy ish rejimi, va u tuzilmaviy sabab bilan sodir bo'ladi.
Provayder sizga callback yuboradi. Servisingiz uni to'g'ri qayta ishlaydi, qatorni yozadi va 200 javobini tuza boshlaydi. Shu payt ulanish uzilib qoladi. Provayder tomonidan qaraganda o'sha chaqiruvning natijasi yo'q — u "so'rov umuman yetib bormadi" bilan "so'rov to'liq qayta ishlandi, lekin javob yo'qoldi" ni ajrata olmaydi. Ikkalasi tashqaridan bir xil ko'rinadi.
Shu noaniqlik oldida har bir jiddiy provayder bir xil tanlov qiladi: qayta yuborish. Ular "kamida bir marta" yetkazishni tanlaydi, chunki to'lov xabarini yo'qotish uni ikki marta yuborishdan yomonroq. Bu tanlov to'g'ri. Bu shuni ham anglatadi: takrorlanish ularning kelajakda tuzatiladigan xatosi emas — bu interfeysning doimiy, ataylab qilingan xususiyati, uni yutish esa sizning vazifangiz.
Uch xil takrorlanish
Mendan eng ko'p vaqt olgan qism shu. "Takrorlanish" — bitta hodisa emas.
Bir xil qayta urinish. Bir xil provayder tranzaksiyasi, bir xil summa, bir xil payload, ikki marta yetkazilgan. Bu keng tarqalgan holat va unikal indeks hal qiladigan holat.
Holat o'zgargandan keyingi qayta yetkazish. Provayder paid yuboradi, keyin bir soatdan so'ng yana paid yuboradi — lekin oraliqda foydalanuvchi qaytarish so'ragan va to'lov endi refunded. Ikkinchi paid ni o'ylamay qo'llash to'lovni allaqachon tark etgan holatiga orqaga qaytaradi. So'rov id'si bo'yicha unikal tekshiruv buni umuman ushlamasligi mumkin, chunki bir soat oralab kelgan urinishlar ba'zan yangi id bilan keladi.
Semantik takrorlanish. Bitta foydalanuvchi niyatini ifodalaydigan ikkita chindan boshqa provayder tranzaksiyasi — klassik ikki marta bosish yoki to'lagan, spinner ko'rgan va yana to'lagan foydalanuvchi. Bular protokol darajasida takrorlanish emas. Ikkalasi ham haqiqiy, ikkalasi ham haqiqiy pul olgan. Hech qanday so'rov-id deduplikatsiyasi bunga tegmaydi. Bu yerda biznes qoidasi kerak, va halol javob odatda "aniqla, jimgina birlashtirma" — belgila, bittasini qaytar, kimgadir ayt.
Agar faqat birinchi turdan himoyalansangiz, o'zingizni himoyalangan his qilasiz va ikkinchi hamda uchinchisidan baribir zarba yeysiz.
Buni aslida nima ishlatadi
Uchta qism, va tartibi muhim.
1. Identifikatsiya nimani anglatishini hal qiling. "So'rovda id bor" emas, balki "bu ikki xabar dunyodagi bitta hodisani tasvirlaydi". Odatda bu kalit (provider, provider_transaction_id) bo'ladi, sizning so'rov id'ingiz emas — chunki ularning qayta urinishlari orasida barqaror qoladigani provayderning identifikatori. Nimani tanlasangiz ham, u ilova mantig'ida emas, bazadagi unikal cheklovda turishi kerak. Koddagi tekshiruv — bu poyga; cheklov — bu kafolat.
CREATE UNIQUE INDEX payment_events_provider_txn_uniq
ON payment_events (provider, provider_transaction_id);2. Holatlar mashinasi noqonuniy o'tishlarni rad etsin. Idempotentlik va holat haqiqiyligi — bir muammoning ikki ko'rinishi. Agar to'lov faqat pending → paid → refunded bo'yicha yura olsa, allaqachon refunded bo'lgan narsaga kelgan paid rad etilishi kerak — takroriy bo'lgani uchun emas, o'tish noto'g'ri bo'lgani uchun. Buni ochiq kodlang va ikkinchi turdagi takrorlanish alohida holat bo'lishdan to'xtaydi.
const ALLOWED: Record<Status, Status[]> = {
pending: ['paid', 'cancelled'],
paid: ['refunded'],
refunded: [],
cancelled: [],
};
function canTransition(from: Status, to: Status): boolean {
return ALLOWED[from].includes(to);
}3. Xato emas, o'sha javobning o'zini qaytaring. Odamlar eng ko'p adashadigan joy shu. Tugallangan va bir xil takroriy so'rov kelganda to'g'ri javob — 409 emas, asl muvaffaqiyatli javob. Ikki marta so'ragan provayder ikkalasida ham bir xil narsani bilishi kerak. Xato unga nimadir noto'g'ri ketganini aytadi, o'zini yaxshi tutadigan provayder esa xatoga javoban — qayta uradi. Siz sikl qurdingiz.
"Tugallangan" so'zi bu yerda katta yuk ko'taryapti. Ikki holat chindan boshqacha va ikkalasiga ham halol javob — 409: bir xil kalit boshqa payload bilan kelishi (siz hech qachon qayta ishlamagan so'rov uchun natija qaytara olmaysiz) va asl so'rov hali jarayonda turganda kelgan takror (natija hali yo'q — shuni ayting, Retry-After bilan). Stripe ham aynan shu ikki holatda shunday ishlaydi. Qoida "hech qachon 409 bermang" emas; qoida — "allaqachon muvaffaqiyatli javob bergan takroringizga 409 bermang".
Bu muammoning ikkinchi yarmi — sizning mijozingiz yuboradigan qayta urinish va uni ikki marta yechishdan to'xtatadigan atomar egallash — bu yerda: Ikki marta yechilgan to'lov
Mantiqqa ishonishdan oldin dalilni yozing
Menga eng ko'p foyda bergan o'zgarish idempotentlik mantig'ida umuman emas edi. U — har bir kirayotgan callback'ni u haqda qaror qabul qilinishidan oldin yozib qo'yadigan, faqat qo'shiladigan jadval: provayder, xom payload, kelgan vaqt, undan ajratilgan identifikatsiya kaliti va servis nima xulosa qilgani.
Bu joy egallaydi va evaziga savollarga javob berish imkonini beradi. "Bu callback ikki marta keldimi, yoki biz uni ikki marta qayta ishladikmi?" — usiz javobsiz savol, va aynan shu savolni sizdan tez, arxitekturangiz bilan qiziqmaydigan odam so'raydi.
U yana bir usulni ochadi, va men uni to'lov yo'lini qayta yozayotgan har kimga tavsiya qilaman: yangi mantiqni avval soya rejimida ishlating. Eski va yangi implementatsiya har bir hodisani ko'radi. Faqat eskisi hal qiluvchi. Yangisi esa nima qilgan bo'lishini yozib boradi. Bir haftadan keyin taqqoslaysiz. Farqlar yo yangi koddagi xato, yo siz hech qachon sezmagan eski koddagi xato — ikkalasini ham yangi yo'l pulni boshqarishidan oldin topish kerak.
Yangi mantiqni birinchi kunidan qaror qabul qiluvchi qilib chiqarish — o'sha farqlarni prodda, haqiqiy tranzaksiyalarda, tasodif belgilagan jadval bo'yicha topish demakdir.
Ikki yil oldingi o'zimga aytadiganim
Idempotentlik — o'rnatiladigan middleware emas. Bu uchta qaror: nima bitta hodisa hisoblanadi, qaysi o'tishlar qonuniy, qayta chaqirgan tomon nima eshitishi kerak — ustiga aslida nima kelganining bardoshli yozuvi.
Middleware versiyasi sizni zerikarli takrorlanishdan himoya qiladi. Uchta qaror esa qiziqlaridan himoya qiladi, va odamlar tashvishlanadigan sabab aynan o'sha qiziqlari.
Ruknlar


