webhook 1transaction - nicepayments/nicepay-manual GitHub Wiki

1 Transaction ์›นํ›… & ์Šน์ธ๊ธˆ์•ก๊ฒ€์ฆ

1 Transaction ๋ฐฉ์‹์—์„œ ์›นํ›…๊ณผ ์Šน์ธ๊ธˆ์•ก๊ฒ€์ฆ API๋ฅผ ํ™œ์šฉํ•œ ์•ˆ์ •์ ์ธ ๊ฒฐ์ œ ์ฒ˜๋ฆฌ ๊ฐ€์ด๋“œ

์™œ ํ•„์š”ํ•œ๊ฐ€?

๋ฌธ์ œ ์ƒํ™ฉ

1 Transaction ๋ฐฉ์‹์€ NICEPAY๊ฐ€ ์ธ์ฆ๊ณผ ์Šน์ธ์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•˜๊ณ  returnUrl๋กœ ๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

๊ณ ๊ฐ ๊ฒฐ์ œ โ†’ NICEPAY ์ธ์ฆ+์Šน์ธ โ†’ returnUrl ์‘๋‹ต (ํƒ€์ž„์•„์›ƒ ๋ฐœ์ƒ!)

๋„คํŠธ์›Œํฌ ํƒ€์ž„์•„์›ƒ ์‹œ:

  • ๊ฒฐ์ œ๋Š” ์„ฑ๊ณตํ–ˆ์ง€๋งŒ ๊ฐ€๋งน์ ์€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์ง€ ๋ชปํ•จ
  • ๊ณ ๊ฐ์€ ๊ฒฐ์ œ ์™„๋ฃŒ ํŽ˜์ด์ง€๋ฅผ ๋ณด์ง€ ๋ชปํ•จ
  • ์ฃผ๋ฌธ ์ƒํƒœ๊ฐ€ ๋ถˆํ™•์‹คํ•œ ์ƒํƒœ๋กœ ๋‚จ์Œ

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์›นํ›…์„ ๋ฐฑ์—… ํ”Œ๋กœ์šฐ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค.

๊ณ ๊ฐ ๊ฒฐ์ œ โ†’ NICEPAY ์ธ์ฆ+์Šน์ธ โ†’ โ”ฌโ†’ returnUrl (๋ฉ”์ธ)
                                  โ””โ†’ ์›นํ›… (๋ฐฑ์—…)

์žฅ์ :

  • โœ… returnUrl ํƒ€์ž„์•„์›ƒ ์‹œ์—๋„ ์›นํ›…์œผ๋กœ ๊ฒฐ๊ณผ ์ˆ˜์‹ 
  • โœ… ๊ฒฐ์ œ ๋ˆ„๋ฝ ๋ฐฉ์ง€
  • โœ… ์•ˆ์ •์ ์ธ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ

์Šน์ธ๊ธˆ์•ก๊ฒ€์ฆ API

ํ•„์š”์„ฑ

returnUrl๋กœ ๋ฐ›์€ ๊ฒฐ์ œ ๊ฒฐ๊ณผ๊ฐ€ ์œ„๋ณ€์กฐ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

API ํ˜ธ์ถœ

curl -X POST 'https://api.nicepay.co.kr/v1/payments/{tid}/amount' \
-H 'Content-type: application/json' \
-H 'Authorization: Basic UjJfeW91cl9jbGllbnRfaWQ6...' \
-d '{
  "amount": 10000
}'

์‘๋‹ต ์˜ˆ์‹œ

๊ธˆ์•ก ์ผ์น˜ ์‹œ:

{
  "resultCode": "0000",
  "resultMsg": "์ •์ƒ ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "tid": "ORD20240115XXXXXXXXXX",
  "amount": 10000,
  "isMatched": true
}

๊ธˆ์•ก ๋ถˆ์ผ์น˜ ์‹œ:

{
  "resultCode": "A001",
  "resultMsg": "๊ธˆ์•ก์ด ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",
  "isMatched": false
}

๊ตฌํ˜„ ํŒจํ„ด

returnUrl ์ฒ˜๋ฆฌ

app.get('/return', async (req, res) => {
  const { tid, orderId, amount, authResultCode } = req.query;

  // 1. ๊ฒฐ์ œ ์„ฑ๊ณต ์—ฌ๋ถ€ ํ™•์ธ
  if (authResultCode !== '0000') {
    return res.send('๊ฒฐ์ œ ์‹คํŒจ');
  }

  // 2. ์Šน์ธ๊ธˆ์•ก๊ฒ€์ฆ
  const isValid = await verifyAmount(tid, amount);
  if (!isValid) {
    console.error('โŒ ๊ธˆ์•ก ๋ถˆ์ผ์น˜ - ์œ„๋ณ€์กฐ ์˜์‹ฌ');
    return res.send('๊ฒฐ์ œ ๊ฒ€์ฆ ์‹คํŒจ');
  }

  // 3. ์ค‘๋ณต ์ฒ˜๋ฆฌ ๋ฐฉ์ง€
  if (await isProcessed(orderId)) {
    return res.send('๊ฒฐ์ œ ์™„๋ฃŒ');
  }

  // 4. ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ
  await processOrder(orderId, tid, amount);
  res.send('๊ฒฐ์ œ ์™„๋ฃŒ');
});

์›นํ›… ์ฒ˜๋ฆฌ

app.post('/webhook/nicepay', async (req, res) => {
  const webhookData = req.body;

  // ์ฆ‰์‹œ OK ์‘๋‹ต (ํ•„์ˆ˜)
  res.status(200).type('text/html').send('OK');

  // ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ
  setImmediate(async () => {
    // 1. Signature ๊ฒ€์ฆ
    if (!verifySignature(webhookData)) {
      return;
    }

    // 2. ์ค‘๋ณต ์ฒ˜๋ฆฌ ๋ฐฉ์ง€
    if (await isProcessed(webhookData.orderId)) {
      return;
    }

    // 3. ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ
    await processOrder(
      webhookData.orderId,
      webhookData.tid,
      webhookData.amount
    );
  });
});

์ค‘๋ณต ์ฒ˜๋ฆฌ ๋ฐฉ์ง€

// Redis๋ฅผ ํ™œ์šฉํ•œ ์ค‘๋ณต ์ฒดํฌ
async function isProcessed(orderId) {
  const key = `order:processed:${orderId}`;
  const exists = await redis.exists(key);

  if (!exists) {
    // 1์‹œ๊ฐ„ ๋™์•ˆ ์ฒ˜๋ฆฌ ์™„๋ฃŒ ์ƒํƒœ ์œ ์ง€
    await redis.setex(key, 3600, '1');
    return false;
  }

  return true;
}

ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค

์‹œ๋‚˜๋ฆฌ์˜ค 1: ์ •์ƒ ํ”Œ๋กœ์šฐ

1. ๊ฒฐ์ œ ์™„๋ฃŒ
2. returnUrl ์ˆ˜์‹  โ†’ ์Šน์ธ๊ธˆ์•ก๊ฒ€์ฆ โ†’ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ
3. ์›นํ›… ์ˆ˜์‹  โ†’ ์ค‘๋ณต ํ™•์ธ โ†’ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๊ฒฐ๊ณผ: โœ… ์ฃผ๋ฌธ 1ํšŒ๋งŒ ์ฒ˜๋ฆฌ

์‹œ๋‚˜๋ฆฌ์˜ค 2: returnUrl ํƒ€์ž„์•„์›ƒ

1. ๊ฒฐ์ œ ์™„๋ฃŒ
2. returnUrl ํƒ€์ž„์•„์›ƒ (๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜)
3. ์›นํ›… ์ˆ˜์‹  โ†’ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ

๊ฒฐ๊ณผ: โœ… ์›นํ›…์œผ๋กœ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ, ๊ฒฐ์ œ ๋ˆ„๋ฝ ์—†์Œ

์‹œ๋‚˜๋ฆฌ์˜ค 3: ๋™์‹œ ์ˆ˜์‹ 

1. ๊ฒฐ์ œ ์™„๋ฃŒ
2. returnUrl๊ณผ ์›นํ›… ๊ฑฐ์˜ ๋™์‹œ ์ˆ˜์‹ 
3. ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ์ด ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ
4. ๋‘ ๋ฒˆ์งธ ์š”์ฒญ์€ ์ค‘๋ณต ํ™•์ธ ํ›„ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๊ฒฐ๊ณผ: โœ… ์ฃผ๋ฌธ 1ํšŒ๋งŒ ์ฒ˜๋ฆฌ

์‹œ๋‚˜๋ฆฌ์˜ค 4: ๊ธˆ์•ก ์œ„๋ณ€์กฐ

1. returnUrl ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ž‘ (amount ๋ณ€๊ฒฝ)
2. ์Šน์ธ๊ธˆ์•ก๊ฒ€์ฆ API ํ˜ธ์ถœ
3. ๊ธˆ์•ก ๋ถˆ์ผ์น˜ ๊ฐ์ง€ โ†’ ์ฒ˜๋ฆฌ ์ค‘๋‹จ

๊ฒฐ๊ณผ: โœ… ์œ„๋ณ€์กฐ ๊ฐ์ง€, ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ์•ˆ ๋จ


์Šน์ธ๊ธˆ์•ก๊ฒ€์ฆ API ์ƒ์„ธ

API ๋ช…์„ธ

์—”๋“œํฌ์ธํŠธ

POST /v1/payments/{tid}/amount

์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ์„ค๋ช…
amount Int O ๊ฒ€์ฆํ•  ๊ธˆ์•ก

์‘๋‹ต ํŒŒ๋ผ๋ฏธํ„ฐ

ํ•„๋“œ ํƒ€์ž… ์„ค๋ช…
resultCode String ๊ฒฐ๊ณผ ์ฝ”๋“œ (0000: ์„ฑ๊ณต)
resultMsg String ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€
tid String ๊ฑฐ๋ž˜๋ฒˆํ˜ธ
amount Int ๊ฒ€์ฆ ์š”์ฒญ ๊ธˆ์•ก
isMatched Boolean ๊ธˆ์•ก ์ผ์น˜ ์—ฌ๋ถ€

curl ์˜ˆ์ œ

curl -X POST 'https://api.nicepay.co.kr/v1/payments/ORD20240115XXX/amount' \
-H 'Content-type: application/json' \
-H 'Authorization: Basic UjJfeW91cl9jbGllbnRfaWQ6...' \
-d '{
  "amount": 10000
}'

์šด์˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

ํ•„์ˆ˜ ๊ตฌํ˜„

  • returnUrl ํ•ธ๋“ค๋Ÿฌ์—์„œ ์Šน์ธ๊ธˆ์•ก๊ฒ€์ฆ API ํ˜ธ์ถœ
  • ์›นํ›… ํ•ธ๋“ค๋Ÿฌ์—์„œ Signature ๊ฒ€์ฆ
  • ์ค‘๋ณต ์ฒ˜๋ฆฌ ๋ฐฉ์ง€ ๋กœ์ง (Redis ๋˜๋Š” DB)
  • returnUrl๊ณผ ์›นํ›… ๋ชจ๋‘ ๋™์ผํ•œ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ ์‚ฌ์šฉ

์•ˆ์ •์„ฑ ํ™•์ธ

  • 4๊ฐ€์ง€ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ๊ฒ€์ฆ
  • ์ค‘๋ณต ์ฃผ๋ฌธ ์ƒ์„ฑ ์—†์Œ
  • ํƒ€์ž„์•„์›ƒ ์‹œ์—๋„ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ๋จ
  • ์œ„๋ณ€์กฐ ์‹œ๋„ ์ฐจ๋‹จ

๋ชจ๋‹ˆํ„ฐ๋ง

  • returnUrl ํƒ€์ž„์•„์›ƒ ๋ฐœ์ƒ ๋นˆ๋„ ํ™•์ธ
  • ์›นํ›…์œผ๋กœ๋งŒ ์ฒ˜๋ฆฌ๋œ ์ฃผ๋ฌธ ๋น„์œจ ํ™•์ธ
  • ์Šน์ธ๊ธˆ์•ก๊ฒ€์ฆ ์‹คํŒจ ์•Œ๋ฆผ ์„ค์ •

๊ด€๋ จ ๋ฌธ์„œ

โš ๏ธ **GitHub.com Fallback** โš ๏ธ