Բոլոր հոդվածները
19 օգոստոսի 2026 թ. · 3 րոպե ընթերցում

Webhook-ները բացատրված. ինչու է խանութը հետ քաշում պատվերի իրական կարգավիճակը

Webhook-ն ասում է Ձեր խանութին, որ ինչ-որ բան տեղի է ունեցել։ Դա ապացույց չէ, որ այն իսկապես տեղի է ունեցել։ Սա այն մասին է, թե ինչպես է Paynet-ը ստորագրում webhook-ները, ինչու պետք է դրանք դիտարկել որպես ազդանշան ու հետ կարդալ վճարումը, և ինչպես ստուգել ստորագրությունը PHP-ով ու Node-ով։

Ձեր գնորդը վճարում է checkout էջում։ Նրանց հեռախոսը կապն է կորցնում Ձեր կայք վերադառնալու ճանապարհին։ Նրանք երբեք չեն հասնում Ձեր շնորհակալության էջին։ Պատվե՞րը վճարվեց արդյոք։

Սա այն խնդիրն է, որի համար գոյություն ունեն webhook-ները, և միևնույն ժամանակ այն խնդիրն է, որի լուծման հարցում webhook-ներին սովորաբար չափից ավելի են վստահում։ Այս գրառումն անդրադառնում է երկուսին էլ. ինչ է Paynet webhook-ը, և ինչու խանութը դեռ պետք է հարցնի ինքն իրեն։

Ինչ է webhook-ը

Webhook-ը HTTP հարցում է, որը Paynet-ն ուղարկում է Ձեր URL-ին, երբ ինչ-որ բան տեղի է ունենում։ Փոխանակ Ձեր սերվերն ամեն մի քանի վայրկյանը մեկ հարցնի «վճարվա՞ծ է արդեն», Paynet-ը թակում է Ձեր դուռը, հենց պատասխանն արդեն հայտնի է։

Paynet-ը մեկը ուղարկում է վճարման ամեն վերջնական կարգավիճակի ժամանակ, ներառյալ անհաջողները։ Ինչպես ձախողված վճարումը, այնպես էլ չվճարված ժամկետանց վճարումը երկուսն էլ առաջացնում են transaction.status.updated իրադարձություն, ոչ միայն հաջողվածները։ Վերադարձներն առաջացնում են payment.refunded իրադարձություն։

Հարցումն ուղարկվում է այն callback_url-ին, որ Դուք սահմանել եք տվյալ վճարման վրա։ Եթե չեք սահմանել, այն ուղարկվում է դոմենի համար կարգավորված webhook URL-ին։

Մարմինը պարունակում է delivery_id, event անունը, livemode դրոշը, timestamp, transaction օբյեկտ՝ uuid-ով, Ձեր order_id-ով, կարգավիճակով, գումարով, վերադարձված գումարով, արժույթով, պրոցեսորով ու ժամանակակնիքներով, գումարած refund օբյեկտ՝ վերադարձի իրադարձությունների վրա, և receipt օբյեկտ՝ կտրոնի id-ով ու QR URL-ով, երբ հարկային կտրոն է թողարկվել։

Ինչու է խանութը հետ քաշում իրական կարգավիճակը

Ահա այն կանոնը, որի շուրջ կառուցված է Paynet-ը. webhook-ը դիտարկեք որպես ազդանշան, և հետ կարդացեք ճշմարտությունը, նախքան պատվերը վճարված նշելը։

Երեք պատճառ կա։

Ցանկացած մեկը կարող է հարցում ուղարկել Ձեր URL-ին։ Ձեր webhook endpoint-ը հանրային URL է ինտերնետում։ Առանց ստուգման, մի անծանոթ, ով գուշակել է այն, կարող է ասել Ձեր խանութին, որ 1042 պատվերը վճարված է։ Ներքևում նկարագրված ստորագրությունը փակում է այդ անցքը, բայց կարգավիճակը հետ քաշելը փակում է այն երկրորդ անգամ, քանի որ պատասխանն այդ դեպքում գալիս է Ձեր իսկ սկսած, հաստատված հարցումից։

Webhook-ը մոմենտալ նկար է, ոչ թե ներկան։ Առաքումները կարող են ուշանալ, հասնել սխալ հերթականությամբ կամ երկու անգամ։ Եթե վճարումը փոփոխվել է այն պահից, երբ իրադարձությունը հերթագրվել է, Ձեր ձեռքի տակ եղած մարմինը հնացած է։

Կարգավիճակները վերջնական չեն այնպես, ինչպես կարող եք սպասել։ Մատակարարից ուշացած հաստատումը կարող է վճարումը failed-ից կամ expired-ից տեղափոխել completed-ի։ Միշտ գործեք ըստ Ձեր կարդացած վերջին վիճակի, ոչ թե առաջին webhook-ի, որ տեսաք։

Այսպիսով, հոսքն այսպիսին է. ստուգեք ստորագրությունը, արագ պատասխանեք 200-ով, բացառեք կրկնօրինակները delivery_id-ի հիման վրա, ապա կանչեք GET /api/v1/orders/{order_id}/payment՝ Ձեր API բանալիով, և գործեք ըստ ստացած պատասխանի։ Այդ endpoint-ը կապված է միայն Ձեր հաշվին, ուստի խանութը կարող է կարդալ միայն սեփական պատվերները, և այն վերադարձնում է պատվերի ամենավերջնական վճարումը. վերջնական գրառումը գերակայում է հնացած սպասող գրառմանը։

Հենց սա են անում պաշտոնական Paynet CMS հավելումները։ Webhook-ը ազդանշան է տալիս խանութին, խանութը հետ է քաշում ճշմարտությունը։ Հենց դա է պատճառը, որ հավելումը շարունակում է ճիշտ աշխատել, նույնիսկ երբ առաքումը կորած է։

Ստորագրությունը

Ամեն առաքում կրում է X-Paynet-Signature վերնագիր։ Դա հում հարցման մարմնի փոքրատառ տասնվեցական HMAC-SHA256 արժեքն է, հաշվարկված Ձեր webhook գաղտնիքով։ Այդ գաղտնիքը կգտնեք վահանակում՝ դոմենի webhook կարգավորումների տակ։

Երեք մանրամասն են որոշում, թե Ձեր ստուգումն իրականում ճիշտ է, թե ոչ։

Օգտագործեք հում բայթերը։ Հաշվարկեք HMAC-ը մարմնի վրա հենց այնպես, ինչպես այն հասել է։ Եթե Ձեր framework-ը վերլուծում է JSON-ը և Դուք այն կրկին կոդավորում եք, բացատները և բանալիների հերթականությունը կարող են փոխվել, և ստորագրությունը չի համընկնի։ Express-ում դա նշանակում է express.raw, ոչ թե express.json։

Համեմատեք հաստատուն ժամանակով։ Օգտագործեք hash_equals PHP-ում կամ crypto.timingSafeEqual Node-ում, ոչ թե ==։ Սովորական համեմատությունը բացահայտում է, թե ստորագրության որքան մասն եք ճիշտ գուշակել։

Ընդունեք փոփոխման վերնագիրը։ Երբ փոփոխում եք Ձեր ստորագրման գաղտնիքը, առաքումները 24 ժամ կրում են երկու ստորագրություններն էլ. X-Paynet-Signature-ը՝ ընթացիկ գաղտնիքով, և X-Paynet-Signature-Next-ը՝ նոր գաղտնիքով։ Ցանկացած մեկի հետ ստուգումը թույլ է տալիս փոփոխել գաղտնիքը՝ առանց մեկ իրադարձություն էլ կորցնելու։

PHP

// PHP: raw body, hex HMAC, constant-time compare, then read the truth back.
$raw    = file_get_contents('php://input');
$secret = getenv('PAYNET_WEBHOOK_SECRET');
$given  = $_SERVER['HTTP_X_PAYNET_SIGNATURE'] ?? '';
$next   = $_SERVER['HTTP_X_PAYNET_SIGNATURE_NEXT'] ?? '';
$mine   = hash_hmac('sha256', $raw, $secret);
if (!hash_equals($mine, $given) && !hash_equals($mine, $next)) { http_response_code(401); exit; }
$event = json_decode($raw, true);
// dedupe on $event['delivery_id'], then:
$payment = json_decode(file_get_contents(
    'https://paynet.am/api/v1/orders/' . rawurlencode($event['transaction']['order_id']) . '/payment',
    false, stream_context_create(['http' => ['header' => "X-Paynet-Key: " . getenv('PAYNET_API_KEY')]])
), true);
if (($payment['status'] ?? null) === 'completed') { /* mark the order paid */ }
http_response_code(200);

Node.js

// Node.js (Express): keep the raw body for the HMAC, never a re-encoded one.
app.post('/webhooks/paynet', express.raw({ type: '*/*' }), async (req, res) => {
  const mine = crypto.createHmac('sha256', process.env.PAYNET_WEBHOOK_SECRET).update(req.body).digest('hex');
  const ok = [req.get('X-Paynet-Signature'), req.get('X-Paynet-Signature-Next')]
    .some(h => h && h.length === mine.length && crypto.timingSafeEqual(Buffer.from(h), Buffer.from(mine)));
  if (!ok) return res.sendStatus(401);
  const event = JSON.parse(req.body);
  // dedupe on event.delivery_id, then confirm:
  const r = await fetch(`https://paynet.am/api/v1/orders/${encodeURIComponent(event.transaction.order_id)}/payment`,
    { headers: { 'X-Paynet-Key': process.env.PAYNET_API_KEY } });
  const payment = await r.json();
  if (payment.status === 'completed') { /* mark the order paid */ }
  res.sendStatus(200);
});

Գաղտնիքն ու API բանալին պահեք environment փոփոխականներում։ Ոչ մեկը տեղ չունի Ձեր repository-ում կամ բրաուզերի կոդում։

Կրկնումները, և ինչ է դրանցից պարտական Ձեր endpoint-ը

Paynet-ը 2xx պատասխանի համար սպասում է 10 վայրկյան։ Այն չի հետևում վերահղումներին և հրաժարվում է առաքել մասնավոր հասցեների։ Եթե առաքումը հաջողությամբ չի ավարտվում, այն կրկնվում է երեք անգամ. 1 րոպե, 5 րոպե և 30 րոպե անց։

Երկու հետևանք է բխում սրանից։

Պատասխանեք արագ, աշխատեք հետո։ Ստուգեք, գրանցեք, վերադարձրեք 200։ Եթե Դուք հարցման ներսում ձևակերպում եք պատվերը, ուղարկում էլ. նամակ և կանչում Ձեր հաշվապահական համակարգը, ի վերջո կգերազանցեք 10 վայրկյանը և կստանաք կրկնումներ արդեն կատարած աշխատանքի համար։ Այդ մասը դրեք հերթում։

Սպասեք կրկնօրինակների։ Կրկնումը կարող է տեղի ունենալ այն պատճառով, որ Ձեր պատասխանը դանդաղ էր, ոչ թե որովհետև երբեք չեք ստացել իրադարձությունը։ Հետևաբար, նույն իրադարձությունը կարող է հասնել երկու անգամ, և երկու անգամ էլ կկրի նույն delivery_id-ը։ Պահեք delivery_id-ը և անտեսեք այն, որ արդեն մշակել եք։ Հենց սա է նշանակում իդեմպոտենտություն այստեղ, և դա webhook handler-ի ամենաօգտակար տողն է։

Եթե ավարտված կամ վերադարձված վճարման առաքումը վերջնականապես ձախողվում է, Paynet-ը Ձեզ էլ. նամակ է ուղարկում, առավելագույնը մեկ անգամ ամեն դոմենի համար վեց ժամում։ Այդ նամակը նշանակում է, որ իրական գումար է շարժվել, և Ձեր խանութը կարող է դեռ չիմանալ այդ մասին։

Թեստային և կենդանի ռեժիմ

Ամեն իրադարձություն կրում է livemode։ Թեստային վճարումները, արված sk_test_ բանալիով կամ Sandbox մատակարարով, ուղարկում են webhook-ներ livemode: false-ով, ուստի կարող եք ապացուցել ամբողջ ուղին սկզբից մինչև վերջ, նախքան մեկ իրական քարտի օգտագործումը։

Օգտագործեք այդ դրոշը զգուշությամբ։ Եթե Ձեր production պատվերների համակարգը երբևէ ստանում է իրադարձություն livemode: false-ով, ինչ-որ բան սխալ է կարգավորված, և ավելի լավ է բարձրաձայն մերժել այն, քան sandbox իրադարձությունից իրական պատվեր վճարված նշել։

Գործիքներ վահանակում

Դուք ստիպված չեք գուշակել, աշխատում են արդյոք առաքումները։

Webhooks էջը ցույց է տալիս, թե քանի դոմեն ունի կարգավորված webhook URL, և քանի առաքում է հաջողվել ու ձախողվել վերջին յոթ օրում։ Առաքման լոգը թվարկում է ամեն փորձ՝ իրադարձությամբ, դոմենով, պատվերով, արդյունքով ու ժամանակով, ներառյալ նրանք, որ ընդհանրապես պատասխան չեն ստացել։ Ցանկացած ձախողված առաքում կարելի է կրկին փորձել այնտեղից։

Կա նաև Send test webhook կոճակ դոմենի webhook կարգավորումներում։ Այն մի քանի վայրկյանում հերթագրում է առաքում, և արդյունքը հայտնվում է լոգում։ Օգտագործեք այն, երբ առաջին անգամ տեղակայում եք Ձեր endpoint-ը, և կրկին՝ Ձեր սերվերի, TLS վկայագրի կամ firewall-ի ցանկացած փոփոխությունից հետո։

Կարճ ստուգացանկ ճիշտ handler-ի համար

  1. Կարդացեք հում մարմինը, նախքան որևէ բան կվերլուծի այն։
  2. Հաշվարկեք HMAC-SHA256 տասնվեցական digest-ը Ձեր webhook գաղտնիքով։
  3. Համեմատեք հաստատուն ժամանակով X-Paynet-Signature-ի հետ, ինչպես նաև X-Paynet-Signature-Next-ի հետ։
  4. Մերժեք 401-ով, եթե ոչ մեկը չի համընկնում։
  5. Բացառեք կրկնօրինակները delivery_id-ի հիման վրա և կանգնեք, եթե արդեն տեսել եք այն։
  6. Անմիջապես վերադարձրեք 200։
  7. Ֆոնային ռեժիմով կանչեք պատվերի կարգավիճակի endpoint-ը և գործեք ըստ վերադարձված կարգավիճակի։
  8. Երբեք մի նշեք պատվերը վճարված՝ միայն webhook-ի մարմնի հիման վրա։

Ինչ անել հաջորդը

  1. Սահմանեք webhook URL Ձեր դոմենի համար վահանակում, կամ ուղարկեք callback_url՝ վճարում ստեղծելիս։
  2. Իրագործեք վերևի handler-ը և պահեք webhook գաղտնիքը environment փոփոխականում։
  3. Սեղմեք Send test webhook և ստուգեք առաքման լոգը։
  4. Կատարեք sandbox վճարում և հաստատեք, որ Ձեր պատվերը վճարվածի է տեղափոխվում հետ քաշման միջոցով, ոչ թե ուղարկման։
  5. Կարդացեք API տեղեկատուի webhook-ների և կենսացիկլի բաժինները, և մշակողների ուղեցույցը՝ ինտեգրման մնացած մասի համար։

Եթե նախընտրում եք ընդհանրապես չգրել այս ամենը, պատրաստի խանութի հավելումները արդեն իրագործում են այն։

Հիշատակված ցանկացած մատակարարի կամ հարթակի անուն պատկանում է իր տիրոջը։ Paynet-ը անկախ վճարային դարպաս է։