Բոլոր դասերը
Էլեկտրոնային առևտուրԱռաջադեմ14 րոպե ընթերցումԻմ ստեկում

Վճարումներ

Գումարի անվտանգ ընդունում՝ gateway-ներ, 3-D Secure, webhook-ներ, idempotency, վերադարձներ և reconciliation։

  • Gateways
  • 3-D Secure
  • Webhooks
  • Refunds
  • Reconciliation
Քայլ 1/5
Հաճախորդը ձևակերպում է պատվերը։ Խանութը ստեղծում է պատվերը «pending» կարգավիճակով։

30 վայրկյանում

Վճարումը զրույց է ձեր խանութի, վճարային gateway-ի, հաճախորդի բանկի և երբեմն հաճախորդի հեռախոսի միջև։ Ձեր գործն է այդ զրույցն անվտանգ սկսել, երբեք չդիպչել քարտերի raw համարներին և գրանցել իրական արդյունքը նույնիսկ այն դեպքում, երբ հաղորդագրությունները գալիս են ուշ, երկու անգամ կամ ընդհանրապես չեն գալիս։ Webhook-ները, idempotency key-երը և reconciliation job-երն են դա հնարավոր դարձնում։

Ինչու եմ օգտագործում

  • Ես օգտագործում եմ այս pattern-ները, որովհետև ինտեգրել եմ վճարման բազմաթիվ եղանակներ՝ բանկային քարտերի gateway-ներ, wallet-ներ, Apple Pay և Google Pay, Stripe և PayPal, և դրանցից յուրաքանչյուրը ձախողվում է յուրովի։
  • Վճարումներն այն տեղն են, որտեղ փոքր bug-ը դառնում է իրական վնաս՝ հաճախորդից երկու անգամ գանձված գումար, առանց վճարման ուղարկված պատվեր, վերադարձ, որը երբեք չհասավ բանկին։
  • Լավ վճարային կոդը դիտմամբ ձանձրալի է՝ բացահայտ վիճակներ, ստորագրված callback-ներ, անվտանգ retry-ներ և job, որը ամեն ինչ կրկնակի ստուգում է։
  • Քարտի տվյալները ձեր սերվերներից դուրս պահելը PCI compliance-ի աշխատանքը նախագծից վերածում է հարցաթերթիկի։

Ինչպես է աշխատում

01Ովքեր են մասնակցում և ինչպես է շարժվում գումարը

Պատկերացրեք չորս կողմ։ Հաճախորդը վճարում է։ Merchant-ը (ձեր խանութը) վաճառում է։ Acquirer-ը merchant-ի բանկն է, որին սովորաբար հասնում են վճարային gateway-ի կամ պրովայդերի միջոցով (Stripe, Adyen, տեղական բանկի վիրտուալ POS)։ Issuer-ը հաճախորդի բանկն է, որը հաստատում կամ մերժում է։ Քարտային ցանցերը, ինչպիսիք են Visa-ն և Mastercard-ը, հաղորդագրություններ են փոխանցում նրանց միջև։ Քարտային վճարումն ունի երկու քայլ՝ authorisation (issuer-ը ռեզերվում է գումարը) և capture (գումարն իրականում գանձվում է)։ Շատ խանութներ capture են անում անմիջապես․ ռեստորանները կամ այն խանութները, որոնք վստահ չեն մնացորդի հարցում, կարող են սկզբում authorise անել, իսկ capture՝ պատվերը հաստատելիս։ Settlement-ը՝ գումարի մուտքը ձեր հաշվին, տեղի է ունենում ավելի ուշ, հաճախ մեկ օր կամ ավելի անց՝ միջնորդավճարները հանած։

  • Authorisation՝ «այս քարտը կարո՞ղ է վճարել 50.00» — միջոցները արգելափակվում են։
  • Capture՝ «հիմա գանձիր 50.00-ը» — կարող է լինել ամբողջական կամ մասնակի։
  • Void՝ չեղարկել authorisation-ը մինչև capture-ը․ refund՝ վերադարձնել գումարը capture-ից հետո։

02Ինտեգրացիայի ոճեր՝ redirect, hosted fields և SDK-ներ

Քարտի տվյալները հավաքելու երեք տարածված եղանակ կա։ Redirect-ը (hosted payment page) հաճախորդին ուղարկում է gateway-ի էջ և հետ՝ ձեր return URL-ին․ ամենապարզն է և compliance-ի ամենափոքր ծավալն ունի, տարածված է բանկային gateway-ների հետ։ Hosted fields-ը կամ ներկառուցված կոմպոնենտները (Stripe Payment Element, Adyen Drop-in) քարտի դաշտերը ցուցադրում են iframe-ների մեջ ձեր էջում, այնպես որ ամեն ինչ native է թվում, իսկ քարտի տվյալներն ուղիղ գնում են պրովայդերին։ Մոբայլ SDK-ները նույնն անում են հավելվածներում, իսկ wallet հավելվածները կարելի է բացել deep link-երով կամ native մոդուլներով։ Ինչ ոճ էլ լինի, flow-ը նույնն է՝ ձեր սերվերը ստեղծում է վճարման session պատվերի համար՝ գումարով և արժույթով, հաճախորդն այն ավարտում է պրովայդերի մոտ, և ձեր սերվերն արդյունքն իմանում է պրովայդերից, երբեք միայն բրաուզերից։

ts
// Server: create a payment session for an existing order (Stripe example)
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export async function startPayment(order: { id: string; total: number; currency: string }) {
  const intent = await stripe.paymentIntents.create(
    {
      amount: order.total, // integer minor units
      currency: order.currency.toLowerCase(),
      metadata: { orderId: order.id },
      automatic_payment_methods: { enabled: true },
    },
    { idempotencyKey: `order-${order.id}-attempt-1` },
  );
  return intent.client_secret; // the client confirms with this; no card data touches us
}

033-D Secure և հաճախորդի ուժեղ նույնականացում

3-D Secure-ը (3DS) այն լրացուցիչ քայլն է, երբ issuer-ը հաճախորդից խնդրում է հաստատել վճարումը, սովորաբար բանկային հավելվածում կամ SMS կոդով։ Ներկայիս տարբերակը՝ EMV 3DS-ը (հաճախ կոչվում է 3DS2), ուղարկում է սարքի և գործարքի հարուստ տվյալներ, այնպես որ շատ վճարումներ անցնում են «frictionless»՝ առանց challenge-ի, և միայն ռիսկայինները են challenge ստանում։ Եվրոպայում PSD2-ով սահմանված strong customer authentication-ը (SCA) դա պարտադիր է դարձնում առցանց քարտային վճարումների մեծ մասի համար․ այլ տեղերում այն կամընտիր է, բայց նվազեցնում է խարդախությունը և սովորաբար խարդախության chargeback-ի պատասխանատվությունը տեղափոխում է issuer-ի վրա։ Ձեզ համար 3DS-ը նշանակում է, որ վճարումն ակնթարթային չէ․ այն կարող է կանգ առնել challenge-ի համար, ապա շարունակվել։ Ձեր flow-ը պետք է կարողանա մշակել «requires action»-ի նման կարգավիճակ և հաճախորդ, որը կիսաճանապարհին փակում է tab-ը։

04Webhook-ներ, callback-ներ և idempotency

Բրաուզերի redirect-ը ձեր կայք վճարման ապացույց չէ․ օգտատերերը փակում են tab-երը, ցանցն ընդհատվում է, և ցանկացած մեկը կարող է ձեռքով մուտքագրել return URL-ը։ Ճշմարտության աղբյուրը server-to-server հաղորդագրությունն է՝ webhook (պրովայդերը կանչում է ձեր endpoint-ը) կամ կարգավիճակի query, որը դուք եք անում պրովայդերին։ Ստուգեք յուրաքանչյուր webhook-ի ստորագրությունը, գտեք վճարումը ձեր սեփական ID-ով և թարմացրեք պատվերը transaction-ի ներսում։ Պրովայդերները կրկնում են webhook-ները, ուստի նույն event-ը կարող է գալ մի քանի անգամ կամ խառը հերթականությամբ․ պահեք մշակված event-ների ID-ները և handler-ը դարձրեք idempotent, այսինքն՝ երկու անգամ աշխատեցնելը նույն էֆեկտն ունենա, ինչ մեկ անգամը։ Idempotency key-եր օգտագործեք նաև ձեր դուրս գնացող կանչերում, որպեսզի կրկնված «create payment»-ը կամ «refund»-ը երբեք երկու անգամ չգանձի կամ չվերադարձնի։

ts
// Webhook handler: verify, deduplicate, apply once
app.post("/webhooks/payments", express.raw({ type: "application/json" }), async (req, res) => {
  const event = stripe.webhooks.constructEvent(
    req.body, req.header("stripe-signature")!, process.env.STRIPE_WEBHOOK_SECRET!,
  );
  await db.transaction(async (tx) => {
    const seen = await tx.processedEvents.insertIgnore({ id: event.id }); // unique key
    if (!seen.inserted) return; // duplicate delivery: already handled
    if (event.type === "payment_intent.succeeded") {
      const orderId = event.data.object.metadata.orderId;
      await tx.orders.markPaidIfPending(orderId); // state machine guards the move
    }
  });
  res.sendStatus(200); // answer fast; heavy work goes to a queue
});

05Վճարման վիճակներ, «outcome unknown» և reconciliation

Վճարումների աղյուսակը պահեք պատվերներից առանձին․ մեկ պատվերը կարող է ունենալ մի քանի փորձ։ Յուրաքանչյուր փորձի տվեք բացահայտ վիճակներ, օրինակ՝ created, pending, requires action, authorised, captured, failed, cancelled, refunded և partially refunded։ Ավելացրեք ևս մեկը, որը սկսնակները մոռանում են՝ outcome unknown։ Այն ծածկում է timeout-ներն ու crash-երը, երբ հարցում եք ուղարկել, բայց պատասխան չեք ստացել․ գումարը կարող է շարժված լինել, կարող է և ոչ։ Երբեք մի գուշակեք։ Նշեք այն որպես unknown, կուրորեն մի կրկնեք նոր գանձումով և թողեք, որ reconciliation job-ը պրովայդերից հարցնի իրական կարգավիճակը։ Reconciliation-ն աշխատում է ժամանակացույցով՝ գտնում է pending կամ unknown վիճակում խրված վճարումները, հարցում է անում gateway-ին, ուղղում է տեղական վիճակը և համեմատում օրական settlement հաշվետվությունները ձեր ledger-ի հետ, որպեսզի յուրաքանչյուր ցենտ հաշվառված լինի։

  • Timeout-ով ավարտված կանչը կրկնեք միայն նույն idempotency key-ով, կամ նախ հարցրեք կարգավիճակը։
  • Alert ուղարկեք այն վճարումների համար, որոնք սպասվածից երկար խրված են ոչ վերջնական վիճակում։
  • Պահեք պրովայդերի raw պատասխանները վեճերի և debugging-ի համար՝ առանց քարտի տվյալների։

06Վերադարձներ, պահված քարտեր, wallet-ներ և PCI scope

Վերադարձները ենթարկվում են նույն կարգապահությանը՝ ստեղծեք refund գրառում, կանչեք պրովայդերին idempotency key-ով և հաստատեք վերջնական կարգավիճակը webhook-ից կամ query-ից, քանի որ բանկային վերադարձները կարող են օրեր տևել։ Աջակցեք մասնակի և ըստ ապրանքի վերադարձներին և երբեք մի վերադարձրեք ավելին, քան capture է արվել։ Պահված քարտերն աշխատում են tokenisation-ի միջոցով․ պրովայդերը պահում է քարտը և ձեզ տալիս token կամ customer ID, որով հետագայում կարող եք գանձել, այնպես որ ցուցադրման համար պահում եք միայն բրենդը, վերջին չորս թվանշանները և ժամկետը։ Apple Pay-ն և Google Pay-ը wallet-ներ են, որոնք պրովայդերին փոխանցում են գաղտնագրված, կոնկրետ սարքին կապված token․ վեբում դրանք պահանջում են domain verification և merchant-ի կարգավորում, բայց տալիս են արագ, բիոմետրիկ checkout։ PCI DSS-ը քարտային ոլորտի անվտանգության ստանդարտն է։ Եթե քարտի տվյալները երբևէ դիպչում են միայն պրովայդերի էջերին կամ iframe-ներին, դուք որակավորվում եք ամենակարճ ինքնագնահատման համար (SAQ A), թեև PCI DSS 4.0.1-ով դեռ պետք է պաշտպանեք այն էջերը, որոնք բեռնում են վճարային script-ները։

Օգտագործեք, երբ

  • Ցանկացած խանութ, ամրագրման կամ բաժանորդագրության պրոդուկտ, որն առցանց գումար է ընդունում։
  • Ձեզ պետք են վճարման մի քանի եղանակներ՝ քարտեր, տեղական wallet-ներ, Apple Pay, Google Pay, կանխիկ կամ POS, մեկ համաձայնեցված պատվերի flow-ի հետևում։
  • Պատվերները պետք է ճիշտ մնան նույնիսկ այն դեպքում, երբ gateway-ները timeout են տալիս կամ կրկնակի callback-ներ ուղարկում։

Մի՛ օգտագործեք, երբ

  • Երբեք մի կառուցեք քարտերի պահպանման կամ մշակման սեփական համակարգ․ օգտագործեք պրովայդերի tokenisation-ը։
  • Շատ փոքր խանութի համար hosted checkout-ը (Shopify, Stripe Checkout, payment link) խուսափում է այս աշխատանքի մեծ մասից։
  • Խուսափեք custom 3DS տրամաբանություն գրելուց․ թողեք, որ այն աշխատեցնի պրովայդերի SDK-ն կամ hosted էջը։

Այլընտրանքներ

  • Stripe / Adyen

    Գլոբալ պրովայդերներ՝ հիանալի API-ներով, hosted fields-ով, wallet-ներով և webhook-ներով․ հասանելիությունն ու միջնորդավճարները տարբերվում են ըստ երկրի։

  • Տեղական բանկային gateway-ներ

    Հաճախ միակ տարբերակն են տեղական քարտերի և արժույթների համար․ սովորաբար redirect-ի վրա են հիմնված՝ ավելի պարզ, հին API-ներով, որոնք կարգավիճակի զգույշ ստուգում են պահանջում։

  • PayPal

    Հայտնի wallet՝ գնորդի պաշտպանության սեփական համակարգով․ ինտեգրվում է որպես redirect կամ կոճակներ, իսկ վերջնական կարգավիճակի համար՝ webhook-ներ։

  • Merchant of record (Paddle, Lemon Squeezy)

    Վաճառում են ձեր անունից և զբաղվում են հարկերով ու compliance-ով․ լավ են թվային ապրանքների համար, ավելի քիչ՝ ֆիզիկական ապրանքների։

Լավագույն պրակտիկաներ

  1. Redirect-ին վերաբերվեք միայն որպես UX․ վճարումը հաստատեք ստուգված webhook-ով կամ սերվերային կարգավիճակի query-ով։
  2. Յուրաքանչյուր webhook handler դարձրեք idempotent և պահեք մշակված event-ների ID-ները։
  3. Create-payment և refund կանչերում օգտագործեք idempotency key-եր։
  4. Մոդելավորեք «outcome unknown» վիճակ և լուծեք այն reconciliation job-ով, երբեք կույր retry-ով։
  5. Քարտի տվյալները ձեր համակարգերից դուրս պահեք redirect-ների, hosted fields-ի և token-ների միջոցով։
  6. Վճարման փորձերը առանձնացրեք պատվերներից և պահեք գումարի յուրաքանչյուր շարժի ledger։
  7. Log արեք պրովայդերի raw պատասխանները (առանց քարտի տվյալների) և alert ուղարկեք խրված վճարումների համար։

Տարածված սխալներ

  • Պատվերը վճարված նշել, որովհետև օգտատերը հայտնվել է success URL-ում։
  • Չստուգել webhook-ների ստորագրությունները՝ թույլ տալով ցանկացածին պատվերները վճարված նշել։
  • Timeout-ով ավարտված գանձումը կրկնել նոր հարցումով և հաճախորդից երկու անգամ գանձել։
  • Քարտերի համարները կամ CVV-ն «ժամանակավորապես» պահել log-երում, cache-երում կամ պահպանվող զամբյուղում։
  • Մոռանալ, որ վերադարձները կարող են ձախողվել կամ օրերով մնալ pending վիճակում։

Խորը վերլուծություն

07Ledger. դրամական շարժերի double-entry մտածողություն

Payments աղյուսակը ցույց է տալիս յուրաքանչյուր փորձի վիճակը, իսկ ledger-ը ցույց է տալիս, թե ուր գնաց յուրաքանչյուր ցենտ։ Double-entry հաշվապահությունում յուրաքանչյուր շարժ գրանցվում է երկու անգամ՝ որպես debit մի հաշվի վրա և credit մյուսի վրա, այնպես որ ընդհանուր գումարները միշտ հավասարակշռված են։ Խանութի համար հաշիվները պարզ են. հաճախորդի դեբիտորական պարտք, provider-ի բալանս, միջնորդավճարներ, refund-ներ և եկամուտ։ 50.00-ի capture արված վճարումը 1.50 միջնորդավճարով դառնում է գրառումներ, որոնք 50.00 տեղափոխում են հաճախորդից provider-ի բալանս, իսկ 1.50՝ provider-ի բալանսից միջնորդավճարներ։ Refund-ներն ու chargeback-ները նոր գրառումներ են, երբեք՝ խմբագրումներ։ Երբ գալիս է օրական settlement-ը, provider-ի բալանսը պետք է նվազի ճիշտ payout-ի գումարով։ Ցանկացած տարբերություն reconciliation-ի արդյունք է՝ ճշգրիտ ամսաթվով և reference-ով, այլ ոչ թե առեղծված հաշվապահի spreadsheet-ում։

sql
CREATE TABLE ledger_entries (
  id          BIGSERIAL PRIMARY KEY,
  journal_id  UUID NOT NULL,          -- groups the entries of one movement
  account     TEXT NOT NULL,          -- customer, provider_balance, fees, refunds
  amount      BIGINT NOT NULL,        -- positive = debit, negative = credit
  currency    CHAR(3) NOT NULL,
  payment_id  BIGINT,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);
 
-- Every journal must balance to zero
SELECT journal_id FROM ledger_entries
GROUP BY journal_id, currency HAVING SUM(amount) <> 0;

08Webhook-ների առաքման իմաստաբանությունը գործնականում

Provider-ները webhook-ները առաքում են at least once, ոչ թե exactly once, և ոչ հերթականությամբ։ Օրինակ՝ Stripe-ը live ռեժիմում ձախողված առաքումները կրկնում է մինչև երեք օր՝ exponential backoff-ով, և նշում է, որ event-ների հերթականությունը երաշխավորված չէ։ Նախագծեք դա հաշվի առնելով։ Կարդացեք request-ի raw body-ն, որովհետև signature verification-ը ձախողվում է, եթե framework-ը parse և նորից serialize է անում այն։ Մերժեք այն event-ները, որոնց ստորագրված timestamp-ը չափազանց հին է (Stripe-ի գրադարանների default-ը հինգ րոպե է), որպեսզի արգելափակեք replay-ները։ Արագ վերադարձրեք 2xx և աշխատանքը տեղափոխեք queue, քանի որ դանդաղ handler-ները առաջացնում են timeout-ներ և ավելի շատ retry-ներ։ Worker-ում մի վստահեք event-ների հերթականությանը. կամ provider-ի API-ից վերցրեք օբյեկտի վերջին վիճակը, կամ անցումները կիրառեք state machine-ի միջոցով, որն անտեսում է հետընթաց քայլերը, օրինակ՝ «succeeded»-ից հետո ուշացած «processing» event-ը։

  • Կրկնությունները հեռացրեք event ID-ով՝ unique constraint-ի միջոցով։
  • Webhook route-ը ազատեք CSRF պաշտպանությունից, բայց երբեք՝ signature-ի ստուգումից։
  • Alert ստացեք, երբ endpoint-ը վերադարձնում է non-2xx պատասխաններ, մինչև retry-ները կսպառվեն։

09Dispute-ներ և chargeback-ներ

Chargeback-ը տեղի է ունենում, երբ քարտապանը խնդրում է իր բանկին չեղարկել վճարումը. խարդախություն, ապրանքը չի ստացվել կամ «չի համապատասխանում նկարագրությանը»։ Provider-ը ձեր բալանսից գանձում է վիճարկվող գումարը գումարած dispute-ի միջնորդավճարը և բացում է dispute՝ ապացույցներ ներկայացնելու վերջնաժամկետով։ Հաղթող ապացույցները կոնկրետ են. առաքման հաստատում հասցեով, ստորագրված անդորրագրեր, log-եր, որոնք ցույց են տալիս հաճախորդի account-ը և սարքը, checkout-ում ընդունված refund-ի քաղաքականություն և նախորդ նամակագրություն։ 3-D Secure նույնականացումը սովորաբար խարդախության dispute-ների պատասխանատվությունը տեղափոխում է issuer-ի վրա, բայց ոչ «ապրանքը չի ստացվել» dispute-ների դեպքում։ Քարտային ցանցերը հետևում են merchant-ների dispute ratio-ին, և դրանց շեմերից վեր մնալը հանգեցնում է տուգանքների կամ քարտերի ընդունման իրավունքի կորստի։ Կանխարգելումն ավելի կարևոր է, քան հաղթելը. հստակ statement descriptor, իրական բողոքների դեպքում արագ refund-ներ և ռիսկային պատվերների fraud screening։

10Վճարման էջի պաշտպանություն PCI DSS 4.0.1-ի ներքո

Նույնիսկ երբ քարտի դաշտերը provider-ի iframe-ում են, հարձակվողները թիրախավորում են դրա շուրջ գտնվող էջը. կոմպրոմետացված script-ը կարող է iframe-ը փոխարինել կեղծ ձևով կամ ծածկել այն (հաճախ կոչվում է e-skimming կամ Magecart-ի ոճի հարձակում)։ PCI DSS 4.0.1-ի 6.4.3 պահանջը պահանջում է վճարման էջերի յուրաքանչյուր script-ի inventory՝ հիմնավորմամբ և integrity-ի մեթոդով, իսկ 11.6.1-ը պահանջում է մեխանիզմ, որը հայտնաբերում է այդ էջերի և դրանց security header-ների չթույլատրված փոփոխությունները։ SAQ A-ի 2025 թվականի հունվարի խմբագրությունում այս երկու կետերը փոխարինվեցին իրավունակության չափանիշով. merchant-ը պետք է հաստատի, որ իր կայքը խոցելի չէ script-ներից եկող հարձակումների նկատմամբ, և Council-ի ուղեցույցն ասում է, որ 6.4.3-ի և 11.6.1-ի տեխնիկաները դա ցույց տալու ճանապարհն են։ Գործնականում՝ checkout-ում քիչ script-ներ, խիստ Content Security Policy, Subresource Integrity ստատիկ ֆայլերի համար և փոփոխությունների մոնիտորինգ։

nginx
# nginx: strict CSP on checkout pages (adjust to your provider's documented domains)
location /checkout {
  add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://js.stripe.com; frame-src https://js.stripe.com https://hooks.stripe.com; connect-src 'self' https://api.stripe.com; img-src 'self' data:; report-to csp" always;
  add_header Reporting-Endpoints 'csp="https://example.com/csp-reports"' always;
  try_files $uri /index.php?$query_string;
}

Իրական իրավիճակներ

01

Պատվերները բանկում վճարված են, բայց խանութում դեռ սպասման մեջ են

Խնդիր

Շաբաթը մի քանի անգամ հաճախորդները ուղարկում են հաջող վճարումների բանկային screenshot-ներ, մինչդեռ նրանց պատվերները մնում են «սպասում է վճարման» կարգավիճակում և հետո չեղարկվում են expiry job-ի կողմից։

Ինչպես լուծել

Webhook-ը կամ չի հասնում, կամ լուռ ձախողվում է։ Ստուգեք provider-ի delivery log-ը non-2xx պատասխանների համար, ապա սովորական պատճառները. parse արված body-ի պատճառով signature-ի ձախողումներ, CSRF middleware, firewall, որն արգելափակում է provider-ին, կամ handler, որը timeout է տալիս։ Ուղղեք endpoint-ը, որպեսզի ստուգի raw body-ն և queue-ում դնելուց հետո վերադարձնի 2xx։ Ապա ավելացրեք safety net-եր. reconciliation job, որը provider-ից հարցնում է մի քանի րոպեից ավելի սպասման մեջ գտնվող յուրաքանչյուր վճարման մասին, գործարկեք այն մինչև expiry job-ը որևէ բան կչեղարկի, և expiry job-ը թող բաց թողնի այն պատվերները, որոնց վճարումը անհայտ կամ processing վիճակում է։ Alert դրեք webhook-ի սխալների հաճախականության վրա։

02

Գիշերվա ժամը 3-ին փոքր մերժված վճարումների ալիք

Խնդիր

Գիշերը հարյուրավոր 1.00-անոց պատվերներ տարբեր քարտերով հարվածում են checkout-ին, դրանց մեծ մասը մերժվում է։ Provider-ը զգուշացնում է account-ի մերժումների բարձր հաճախականության մասին։

Ինչպես լուծել

Սա card testing է. խարդախները ձեր checkout-ով ստուգում են գողացված քարտերը։ Արձագանքեք շերտերով։ Անմիջապես rate limit դրեք վճարման փորձերի վրա՝ ըստ IP-ի, սարքի, account-ի և զամբյուղի, և կրկնվող ձախողումներից հետո ավելացրեք challenge (CAPTCHA կամ bot-protection service)։ Եթե հարձակումը շարունակվում է, checkout-ի համար պահանջեք login կամ հաստատված հեռախոսահամար։ Միացրեք provider-ի fraud կանոնները, օրինակ՝ արգելափակում, երբ CVC-ի կամ փոստային ինդեքսի ստուգումը ձախողվում է, և սահմանեք պատվերի նվազագույն արժեք։ Չեղարկեք հարձակման արդյունքում ստեղծված պատվերները և refund արեք հաջողվածները, քանի որ դրանք կդառնան chargeback-ներ։ Դրանից հետո ավելացրեք մոնիտորինգ՝ մերժումների ժամային հաճախականության վրա, որպեսզի հաջորդ հարձակումը րոպեների ընթացքում alert առաջացնի։

03

Երկրորդ payment provider-ի ավելացում

Խնդիր

Բիզնեսը Stripe-ի կողքին ուզում է տեղական բանկային gateway այն հաճախորդների համար, որոնց քարտերը միջազգային վճարումներում ձախողվում են։ Վճարման կոդը ներկայումս Stripe-ը կանչում է անմիջապես checkout controller-ից։

Ինչպես լուծել

Նախ ներմուծեք provider-agnostic վճարման interface՝ createSession, getStatus, refund և parseWebhook, որը վերադարձնում է ձեր սեփական վճարման վիճակները, այլ ոչ թե provider-ին հատուկները։ Գոյություն ունեցող Stripe կոդը փաթեթավորեք adapter-ի մեջ այդ interface-ի հետևում և այդ refactor-ը release արեք առանձին՝ առանց վարքագծի փոփոխության։ Ապա ավելացրեք բանկի adapter-ը, որը հավանաբար redirect-ի վրա հիմնված կլինի և հարուստ webhook-ների փոխարեն status polling կպահանջի։ Յուրաքանչյուր վճարման փորձի վրա պահեք provider-ի անունը, refund-ները ուղղեք այն provider-ին, որը capture է արել գումարը, և թող reconciliation-ը անցնի provider-ների վրայով։ Feature flag-ը կարող է հաճախորդների որոշ տոկոսը կամ քարտերի կոնկրետ երկրները ուղղել նոր gateway, մինչ դուք հետևում եք հաջողության ցուցանիշներին։

Արագ ուղեցույց

Webhook-ների փոխանցում local սերվեր
stripe listen --forward-to localhost:4242/webhooks/payments
Թեստային event-ի գործարկում
stripe trigger payment_intent.succeeded
Անցյալ event-ի կրկնակի ուղարկում endpoint-ին
stripe events resend evt_123 --webhook-endpoint=we_123
Idempotent API կանչ (curl)
curl https://api.stripe.com/v1/refunds -u sk_test_...: -H "Idempotency-Key: refund-order-1042-1" -d payment_intent=pi_123
Webhook event-ների կրկնությունների հեռացում
CREATE UNIQUE INDEX processed_events_uq ON processed_events (provider, event_id);
Խրված վճարումների որոնում
SELECT id FROM payments WHERE status IN ('pending','requires_action','unknown') AND updated_at < now() - interval '15 minutes';
Apple Pay domain verification ֆայլ
https://example.com/.well-known/apple-developer-merchantid-domain-association
Stripe-ի թեստային քարտ, որը պահանջում է 3DS
4000 0027 6000 3184 (ցանկացած ապագա ժամկետ, ցանկացած CVC)

Իմ իրական նախագծերում

  • Ռեստորանային ցանցի պատվերների պլատֆորմում ես ինտեգրեցի քարտային վճարումներ պահված քարտերով, Apple Pay, Google Pay, մի քանի տեղական wallet (մեկը՝ custom native Android մոդուլի միջոցով), POS տերմինալ և կանխիկ՝ վճարման ինը եղանակ մեկ պատվերի flow-ի հետևում։
  • Multi-tenant POS SaaS-ում վճարումների մոդուլն ունի gateway session-ներ, պահված քարտեր, ledger, վերադարձներ՝ ներառյալ «outcome unknown» վիճակը, callback-ներ և reconciliation job, ինչպես նաև idempotency key-եր գործընկերների պատվերների համար։
  • Ես նաև ինտեգրել եմ Stripe և PayPal webhook-ներով, բանկային gateway՝ ստորագրված հարցումներով և pending վճարումների reconciliation-ով, և ըստ ապրանքի վերադարձներ՝ մեկ այլ քարտային gateway-ի միջոցով։

Հարցեր և պատասխաններ

Q1Ինչո՞ւ չի կարելի վստահել success redirect-ին։

Redirect-ը տեղի է ունենում հաճախորդի բրաուզերում, որը դուք չեք վերահսկում։ Tab-ը կարող է փակվել մինչև redirect-ը, ցանցը կարող է ձախողվել, և օգտատերը կարող է պարզապես ձեռքով բացել success URL-ը։ Ուստի redirect-ը միայն բարեհամբույր էջ է ցույց տալիս։ Իրական որոշումը գալիս է պրովայդերից՝ ստորագրված webhook կամ server-to-server կարգավիճակի query՝ ձեր payment ID-ով։ Միայն այդ հաստատումն է պատվերը pending-ից տեղափոխում paid։ Եթե webhook-ն ուշանում է, success էջը կարող է poll անել ձեր backend-ը, մինչև կարգավիճակը վերջնական դառնա։

Q2Ի՞նչ է idempotency-ն և ինչո՞ւ է այն կարևոր վճարումներում։

Գործողությունն idempotent է, երբ այն երկու անգամ կատարելը նույն էֆեկտն ունի, ինչ մեկ անգամը։ Վճարումները լի են retry-ներով՝ պրովայդերները նորից ուղարկում են webhook-ները, client-ները կրկնում են timeout-ի դեպքում, queue-ները նորից առաքում են job-երը։ Առանց idempotency-ի retry-ը կարող է երկու անգամ գանձել կամ երկու անգամ վերադարձնել։ Դուրս գնացող կանչերում ուղարկում եմ պատվերին և փորձին կապված idempotency key, այնպես որ պրովայդերը վերադարձնում է սկզբնական արդյունքը։ Մուտքային webhook-ների համար պահում եմ event ID-ները unique constraint-ով և բաց եմ թողնում կրկնօրինակները, իսկ state machine-ի պաշտպանությունները թույլ չեն տալիս նույն անցումը երկու անգամ կիրառել։

Q3Ինչպե՞ս եք վարվում վճարման հարցման հետ, որն ավարտվել է timeout-ով։

Timeout-ը նշանակում է, որ արդյունքն անհայտ է՝ գանձումը կարող էր հաջողված լինել։ Փորձը նշում եմ որպես outcome unknown և նոր գանձում չեմ ստեղծում։ Եթե պրովայդերն աջակցում է, կրկնում եմ նույն idempotency key-ով, որն անվտանգ վերադարձնում է սկզբնական արդյունքը։ Հակառակ դեպքում հարցնում եմ վճարման կարգավիճակը իմ reference-ով։ Reconciliation job-ը նույնպես վերցնում է այն ամենը, ինչ մի քանի րոպե անց դեռ unknown է։ Հաճախորդը տեսնում է «հաստատում ենք ձեր վճարումը»՝ սխալի փոխարեն, որը կդրդեր նրան նորից վճարել։

Q4Ի՞նչ է 3-D Secure-ը և ինչպե՞ս է այն ազդում ձեր flow-ի վրա։

3-D Secure-ը նույնականացման քայլ է, որն իրականացնում է հաճախորդի բանկը, սովորաբար բանկային հավելվածում հաստատում կամ մեկանգամյա կոդ։ EMV 3DS-ը ուղարկում է սարքի և գործարքի տվյալներ, որպեսզի ցածր ռիսկով վճարումներն անցնեն առանց challenge-ի։ Եվրոպայում PSD2-ի strong customer authentication-ը այն պարտադիր է դարձնում քարտային վճարումների մեծ մասի համար․ այլ տեղերում այն նվազեցնում է խարդախությունը և սովորաբար խարդախության chargeback-ի պատասխանատվությունը տեղափոխում issuer-ի վրա։ Կոդում սա նշանակում է, որ վճարումը կարող է մտնել requires-action վիճակ և ավարտվել ասինխրոն, ուստի flow-ը պետք է հենվի webhook-ների վրա և մշակի լքված challenge-ները։

Q5Ինչպե՞ս եք նվազեցնում PCI DSS scope-ը։

Ապահովելով, որ քարտի տվյալները երբեք չդիպչեն ձեր սերվերներին։ Օգտագործեք hosted payment page կամ hosted fields և SDK կոմպոնենտներ, որոնք ցուցադրվում են պրովայդերի iframe-ներում, և պահված քարտերի համար պահեք միայն token-ներ։ Այդ դեպքում տիպիկ e-commerce merchant-ը կարող է օգտագործել SAQ A-ն՝ ամենակարճ ինքնագնահատման հարցաթերթիկը։ PCI DSS 4.0.1-ով դեռ պետք է հաստատեք, որ ձեր կայքը պաշտպանված է script-ային հարձակումներից, որոնք կարող են ազդել վճարման էջի վրա, ուստի վերահսկեք third-party script-ները, օգտագործեք խիստ Content Security Policy և թարմ պահեք dependency-ները։

Q6Ի՞նչ է անում reconciliation job-ը։

Այն պլանավորված անվտանգության ցանց է, որը ձեր գրառումները համաձայնեցնում է պրովայդերի գրառումների հետ։ Ընտրում է այն վճարումները, որոնք սպասվածից երկար են pending, requires action կամ outcome unknown վիճակում, յուրաքանչյուրի համար հարցում է անում gateway-ին և կիրառում իրական կարգավիճակը նույն idempotent code path-ով, ինչ webhook-ները։ Երկրորդ մասը համեմատում է պրովայդերի settlement կամ transaction հաշվետվությունը ձեր ledger-ի հետ՝ ըստ ամսաթվի, և նշում անհամապատասխանությունները՝ բացակայող վճարումներ, գումարների տարբերություններ, վերադարձներ առանց գրառումների։ Այն ամենը, ինչ չի կարող ավտոմատ ուղղել, դառնում է alert մարդու համար։

Սենիոր մակարդակի հարցեր

S1Ինչպե՞ս եք պահում պատվերի վիճակի և payment provider-ի համաձայնեցվածությունը առանց distributed transaction-ների։

Ես ընդունում եմ, որ դրանք երկու համակարգ են, և նախագծում եմ eventual consistency-ի համար՝ հստակ սեփականությամբ։ Provider-ը տիրապետում է գումարի մասին ճշմարտությանը, իմ database-ը՝ պատվերների մասին ճշմարտությանը։ Յուրաքանչյուր ելքային կանչ ունի idempotency key, ուստի retry-ները անվտանգ են։ Յուրաքանչյուր մուտքային ազդանշան՝ webhook, status query կամ reconciliation-ի արդյունք, անցնում է մեկ idempotent ֆունկցիայով, որը provider-ի վիճակները map է անում իմ վիճակներին և կիրառում state machine-ի անցումները։ Երբ չեմ կարող իմանալ արդյունքը, գրանցում եմ «unknown»՝ գուշակելու փոխարեն։ Reconciliation job-ը ժամանակացույցով փակում է շրջանը։ Գինն այն է, որ համակարգը կարճ ժամանակով անհամաձայնեցված է՝ վայրկյաններ կամ րոպեներ, ուստի UI-ը պետք է ցույց տա ազնիվ միջանկյալ վիճակներ, օրինակ՝ «վճարումը հաստատվում է», այլ ոչ թե ձևացնի, թե ամեն ինչ ակնթարթային է։

S2Authorise անել և capture անել ավելի ուշ, թե՞ capture անել անմիջապես։

Անմիջական capture-ը ավելի պարզ է և հարմար է այն խանութներին, որոնք առաքում են պահեստում առկա ապրանքը կամ վաճառում են թվային ապրանքներ։ Առանձին authorisation-ը և capture-ը հարմար են այն դեպքերին, երբ վերջնական գումարը կամ հասանելիությունը անորոշ է. կշռվող ապրանքներ, ռեստորաններ, որոնք կարող են մերժել պատվերը, marketplace-ներ, որոնք սպասում են վաճառողին, կամ հյուրանոցներ։ Միայն առաքվածի capture-ը խուսափում է refund-ներից, որոնք միջնորդավճար արժեն և օրեր են պահանջում հաճախորդին հասնելու համար, մինչդեռ authorisation-ի void-ը արագ ազատում է ամրագրումը։ Գինը բարդությունն ու ժամկետն են. authorisation-ները ժամկետանց են դառնում, առցանց քարտային վճարումների համար սովորաբար մոտ յոթ օր հետո՝ կախված ցանցից և provider-ից, ուստի պետք է job, որը ժամանակին capture կամ void կանի, իսկ մասնակի capture-ները պետք է աջակցվեն ձեր provider-ի և gateway-ի կողմից։ Ես ընտրում եմ ըստ բիզնես flow-ի, ոչ թե գլոբալ։

S3Ինչպե՞ս կնախագծեիք refund-ները ակցիաներով, wallet-ներով և վճարման մի քանի եղանակներով համակարգի համար։

Refund-ը նախ բիզնես հաշվարկ է, հետո՝ դրամական շարժերի հավաքածու։ Հաշվարկն օգտագործում է պատվերի snapshot-ները. տողի վճարված գումարը՝ դրան բաշխված զեղչից հետո, դրա հարկը և առաքման վճարի քաղաքականությունը։ Ապա գումարը բաշխվում է սկզբնական վճարման միջոցների միջև սահմանված հերթականությամբ՝ սովորաբար նախ վերադարձ քարտին, հետո wallet կամ store credit, և երբեք ավելի, քան յուրաքանչյուր միջոցով capture է արվել։ Յուրաքանչյուր մաս դառնում է իր առանձին refund գրառումը՝ idempotency key-ով, և կատարվում է իր provider-ի միջոցով, իսկ wallet-ի մասերը ledger-ի գրառումներ են։ Պատվերի համար տրված cashback-ը չեղարկվում է համամասնորեն։ Մասնակի ձախողումները սպասելի են, ուստի refund-ն ունի իր սեփական state-ը և reconciliation-ը։ Ամենաբարդ մասը բիզնես կանոնների նախապես համաձայնեցումն է, հատկապես պատվերի ընդհանուր գումարից կախված զեղչերի համար։

Բառարան

Acquirer
Merchant-ի բանկը կամ processor-ը, որն ընդունում է քարտային վճարումները և միջոցները փոխանցում merchant-ին։
Issuer
Հաճախորդի բանկը, որը թողարկել է քարտը և հաստատում կամ մերժում է յուրաքանչյուր վճարում։
Authorisation
Issuer-ի կողմից վճարման համար միջոցների ամրագրում՝ առանց դրանք դեռ տեղափոխելու։
Capture
Նախապես authorise արված միջոցների փաստացի գանձում՝ ամբողջական կամ մասնակի։
Settlement
Capture արված միջոցների payout-ը՝ միջնորդավճարները հանած, provider-ից merchant-ի բանկային հաշվին։
Chargeback
Քարտապանի բանկի նախաձեռնած վճարման չեղարկում, սովորաբար միջնորդավճարով և ապացույցների վերջնաժամկետով։
Liability shift
Խարդախության վնասների պատասխանատվության տեղափոխում merchant-ից issuer-ին, սովորաբար հաջող 3-D Secure նույնականացումից հետո։
Tokenisation
Քարտի տվյալների փոխարինում token-ով, որը միայն provider-ը կարող է նորից վերածել գանձման։
Idempotency key
Հարցման հետ ուղարկվող եզակի արժեք, որպեսզի retry-ները վերադարձնեն առաջին արդյունքը՝ գործողությունը չկրկնելով։
SAQ A
PCI-ի ամենակարճ ինքնագնահատումը այն merchant-ների համար, որոնք քարտային տվյալների մշակումն ամբողջությամբ պատվիրակում են համապատասխան provider-ին։

Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։

Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։