ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • nestjs, vue 로 nicePay 연동 및 테스트
    web/vue3 2024. 3. 3. 13:50

    회사에서 결제서비스를 추가하는데 nicePay가 잘안된다

    제목처럼 vue랑 nest로 하고있는데 문제가 좀 있어서 이해를 위해 집에서 함더 해보자

    먼저 
    nest설치후

    nest g module payment
    nest g controller payment
    nest g service payment

    해서 각각 모듈 컨트롤러 서비스를 만들어주자

    nicePay의 경우 키값을 날짜 + 가격 + 상점아이디 등을 더한후 sha256 으로 암호화 해준다 그래서

    yarn add crypto-js

    [NICEPAY]인증결제-샘플-NODEJS-3.2.0.zip
    3.72MB

     

    nice 공홈에서도 찾아볼 수 있는 샘플이다 

    nodejs express, ejs 로 되어있다

    아무튼 nest 계속 해보자 

    우선 무얼해야하나

    1. 프론트에서 결제정보를 인증요청해서 백으로 보내면 그걸 승인해준다

    2. 마찬가지로 환불쪽을 승인해준다

    3. 그 후 넘어갈 페이지로 보내준다


    로 생각했는데 아니고 완성된 코드부터 살펴보자

    먼저 payment.controller.ts

    // payment.controller.ts
    import { Body, Controller, Get, Post } from '@nestjs/common';
    import { PaymentService } from './payment.service';
    
    @Controller('payment')
    export class PaymentController {
      constructor(private readonly paymentService: PaymentService) {}
    
      @Post('/authReq')
      async authReq(@Body() body: any) {
        console.log('authReq 호출됨', body); 
        return await this.paymentService.sendAuthRequest(body);
      }
      @Get('/test')
      async test() {
        return 'test';
      }
    }

    주요한 코드는 /authReq 다 여기서 post 요청을 만들고 body로 데이터를를 받는다

     

    그다음 코드가 들어간 payment.service.ts

    // payment.service.ts
    import { HttpService } from '@nestjs/axios';
    import { Injectable } from '@nestjs/common';
    import * as CryptoJS from 'crypto-js';
    import * as moment from 'moment';
    import { lastValueFrom } from 'rxjs';
    
    //moment.js를 사용하여 날짜를 YYYYMMDDHHmmss 형식으로 변환하는 함수
    function convertDateToCustomFormat(dateString: string) {
      return moment(dateString).format('YYYYMMDDHHmmss');
    }
    
    
    @Injectable()
    export class PaymentService {
      constructor(private httpService: HttpService) { }
    
      async sendAuthRequest(data: any) {
        console.log('sendAuthRequest 호출됨', data);
        const merchantKey =
          "EYzu8jGGMfqaDEp76gSckuvnaHHu+bC4opsSN6lHv3b2lurNYkVXrZ7Z1AoqQnXI3eLuaUFyoRNC6FkrzVjceg==";
    
          
        let txTid = data.TxTid;
        let authToken = data.AuthToken;
        let mid = data.MID; // MID 속성이 data 객체에 있는지 확인하세요.
        let amt = data.Amt; // Amt 속성이 data 객체에 있는지 확인하세요.
        let ediDate = data.EdiDate; // EdiDate 속성이 data 객체에 있는지 확인하세요.
        const formattedEdiDate = convertDateToCustomFormat(ediDate);
        // SHA256 해시 생성
        const signData = CryptoJS.SHA256(authToken + mid + amt + formattedEdiDate + merchantKey).toString(CryptoJS.enc.Hex);
    
        // URLSearchParams를 사용하여 POST 요청 본문 구성
        const formData = new URLSearchParams();
        formData.append('TID', txTid);
        formData.append('AuthToken', authToken);
        formData.append('MID', mid);
        formData.append('Amt', amt);
        formData.append('EdiDate', formattedEdiDate);
        formData.append('SignData', signData);
    
        console.log('결제 승인 요청 데이터:', formData.toString());
    
        // 요청 옵션 구성
        const options = {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        };
    
        try {
          // 결제 승인 요청 보내기
          const response = await lastValueFrom(this.httpService.post('https://webapi.nicepay.co.kr/webapi/pay_process.jsp', formData, options));
          console.log(`결제 승인 요청 응답:`, response.data);
          return response.data;
        } catch (error) {
          console.error(`결제 승인 요청 에러:`, error);
          throw error;
        }
      }
    }

    몇가지 들어가는데 먼저 암호화를 위한 CryptoJs, yyymmddhhmmss 형식으로 날짜변환을 위한 moment 정도가 있다 

    프론트에서 txtid, authToken, mid, amt, ediDate등을 받아서 

    https://webapi.nicepay.co.kr/webapi/pay_process.jsp 로 보내주면된다....

    여기까진 쉬운데 사실 프론트 쪽이 큰문제였다 

    프론트는 vue로 작성했는데 

    <script setup>
    import axios from "axios";
    import CryptoJS from "crypto-js";
    import moment from 'moment';
    import { computed, onBeforeUnmount, onMounted, onUnmounted, ref } from "vue";
    
    const paymentMessage = ref(null);
    
    // nicepaySubmit 함수를 window 객체에 할당하여 전역에서 접근 가능하게 만듭니다.
    function nicepaySubmit() {
      console.log("폼 제출 시작");
      console.log("폼 제출 명령 실행됨");
      document.payForm.submit();
    }
    
    onMounted(() => {
      // 컴포넌트가 마운트될 때 window 객체에 nicepaySubmit 함수를 할당합니다.
      window.nicepaySubmit = nicepaySubmit;
    });
    
    onUnmounted(() => {
      // 컴포넌트가 언마운트될 때 window 객체에서 nicepaySubmit 함수를 제거합니다.
      delete window.nicepaySubmit;
    });
    // 메시지 리스너 함수
    const handleMessage = (event) => {
      // 여기에서 event.data를 검사하고 처리합니다.
      // 예를 들어, goPay 함수로부터의 응답을 여기에서 처리할 수 있습니다.
      console.log("Received message:", event.data);
    };
    
    onMounted(() => {
      window.addEventListener("message", handleMessage);
    });
    
    onBeforeUnmount(() => {
      window.removeEventListener("message", handleMessage);
    });
    
    const merchantKey =
      "EYzu8jGGMfqaDEp76gSckuvnaHHu+bC4opsSN6lHv3b2lurNYkVXrZ7Z1AoqQnXI3eLuaUFyoRNC6FkrzVjceg==";
    const merchantID = "nicepay00m";
    const ediDate = moment().format('YYYYMMDDHHmmss');
    const amt = ref("1004");
    const returnURL = "";
    const goodsName = ref("나이스상품");
    const moid = ref("nice_api_test_3.0");
    const buyerName = ref("구매자");
    const buyerEmail = ref("happy@day.com");
    const buyerTel = ref("00000000000");
    
    const count = ref(0);
    
    const hashString = computed(() => {
      const str = `${ediDate}${merchantID}${amt.value}${merchantKey}`;
      return CryptoJS.SHA256(str).toString(CryptoJS.enc.Hex);
    });
    
    async function nicepayStart(event) {
      event.preventDefault();
      const paymentData = {
        merchantID,
        ediDate,
        amt: amt.value,
        returnURL,
        goodsName: goodsName.value,
        moid: moid.value,
        buyerName: buyerName.value,
        buyerEmail: buyerEmail.value,
        buyerTel: buyerTel.value,
        hashString: hashString.value,
      };
      goPay(document.payForm);
    }
    
    async function sendPaymentData(paymentData) {
      try {
        const response = await axios.post("/payment/authReq", paymentData, {
          headers: {
            "Content-Type": "application/json",
          },
        });
        console.log("결제 요청 성공:", response.data);
      } catch (error) {
        console.error("결제 요청 실패:", error);
      }
    }
    
    async function test() {
      try {
        const response = await axios.get("/payment/test");
        console.log("test get  요청", response.data);
      } catch (error) {
        console.error("결제 요청 실패:", error);
      }
    }
    
    function nicepayClose() {
      alert("결제가 취소 되었습니다");
    }
    </script>
    
    
    <template>
      <body>
        <form
          name="payForm"
          method="post"
          action="http://localhost:3000/payment/authReq"
          accept-charset="euc-kr"
        >
          <table>
            <tr>
              <th>PayMethod</th>
              <td><input type="text" name="PayMethod" v-model="payMethod" /></td>
            </tr>
            <tr>
              <th>GoodsName</th>
              <td><input type="text" name="GoodsName" v-model="goodsName" /></td>
            </tr>
            <tr>
              <th>Amt</th>
              <td><input type="text" name="Amt" v-model="amt" /></td>
            </tr>
            <tr>
              <th>MID</th>
              <td><input type="text" name="MID" v-model="merchantID" /></td>
            </tr>
            <tr>
              <th>Moid</th>
              <td><input type="text" name="Moid" v-model="moid" /></td>
            </tr>
            <tr>
              <th>BuyerName</th>
              <td><input type="text" name="BuyerName" v-model="buyerName" /></td>
            </tr>
            <tr>
              <th>BuyerEmail</th>
              <td>
                <input type="text" name="BuyerEmail" v-model="buyerEmail" />
              </td>
            </tr>
            <tr>
              <th>BuyerTel</th>
              <td><input type="text" name="BuyerTel" v-model="buyerTel" /></td>
            </tr>
            <tr>
              <th>ReturnURL [Mobile only]</th>
              <td><input type="text" name="ReturnURL" v-model="returnURL" /></td>
            </tr>
            <tr>
              <th>Virtual Account Expiration Date(YYYYMMDD)</th>
              <td><input type="text" name="VbankExpDate" value="" /></td>
            </tr>
    
            <input type="hidden" name="NpLang" value="KO" />
            <!-- EN:English, CN:Chinese, KO:Korean -->
            <input type="hidden" name="GoodsCl" value="1" />
            <!-- products(1), contents(0)) -->
            <input type="hidden" name="TransType" value="0" />
            <!-- USE escrow false(0)/true(1) -->
            <input type="hidden" name="CharSet" value="utf-8" />
            <!-- Return CharSet -->
            <input type="hidden" name="ReqReserved" value="" />
            <!-- mall custom field -->
    
            <!-- DO NOT CHANGE -->
            <input type="hidden" name="EdiDate" v-model="ediDate" />
            <!-- YYYYMMDDHHMISS -->
            <input type="hidden" name="SignData" v-model="hashString" />
            <!-- EncryptData -->
          </table>
          <a href="#" class="btn_blue" @click="nicepayStart">REQUEST</a>
          <a href="#" class="btn_blue" @click="test">test</a>
        </form>
      </body>
    </template>
    
    
    <style scoped>
    </style>

    코드를 보면  handleMessage 같은걸로 콘솔 메세지 붙잡아서 데이터에 담고 뭐 어쩌고 하려는 노력이 보인다

    사실 다 쓸대 없는 짓이다

    <script setup>
    import axios from "axios";
    import CryptoJS from "crypto-js";
    import moment from "moment";
    import { computed, onMounted, onUnmounted, ref } from "vue";
    
    const paymentMessage = ref(null);
    
    // nicepaySubmit 함수를 window 객체에 할당하여 전역에서 접근 가능하게 만듭니다.
    function nicepaySubmit() {
      console.log("폼 제출 시작");
      console.log("폼 제출 명령 실행됨");
      document.payForm.submit();
    }
    
    function nicepayClose() {
      alert("결제가 취소 되었습니다");
    }
    
    onMounted(() => {
      // 컴포넌트가 마운트될 때 window 객체에 nicepaySubmit 함수를 할당합니다.
      window.nicepaySubmit = nicepaySubmit;
      window.nicepayClose = nicepayClose;
    });
    
    onUnmounted(() => {
      // 컴포넌트가 언마운트될 때 window 객체에서 nicepaySubmit 함수를 제거합니다.
      delete window.nicepaySubmit;
      delete window.nicepayClose;
    });
    
    const merchantKey =
      "EYzu8jGGMfqaDEp76gSckuvnaHHu+bC4opsSN6lHv3b2lurNYkVXrZ7Z1AoqQnXI3eLuaUFyoRNC6FkrzVjceg==";
    const merchantID = "nicepay00m";
    const ediDate = moment().format("YYYYMMDDHHmmss");
    const amt = ref("1004");
    const returnURL = "";
    const goodsName = ref("나이스상품");
    const moid = ref("nice_api_test_3.0");
    const buyerName = ref("구매자");
    const buyerEmail = ref("happy@day.com");
    const buyerTel = ref("00000000000");
    
    const count = ref(0);
    
    const hashString = computed(() => {
      const str = `${ediDate}${merchantID}${amt.value}${merchantKey}`;
      return CryptoJS.SHA256(str).toString(CryptoJS.enc.Hex);
    });
    
    async function nicepayStart(event) {
      event.preventDefault();
      goPay(document.payForm);
    }
    
    async function test() {
      try {
        const response = await axios.get("/payment/test");
        console.log("test get  요청", response.data);
      } catch (error) {
        console.error("결제 요청 실패:", error);
      }
    }
    </script>
    <template>
      <body>
        <form
          name="payForm"
          method="post"
          action="http://localhost:3000/payment/authReq"
          accept-charset="euc-kr"
        >
          <table>
            <tr>
              <th>PayMethod</th>
              <td><input type="text" name="PayMethod" v-model="payMethod" /></td>
            </tr>
            <tr>
              <th>GoodsName</th>
              <td><input type="text" name="GoodsName" v-model="goodsName" /></td>
            </tr>
            <tr>
              <th>Amt</th>
              <td><input type="text" name="Amt" v-model="amt" /></td>
            </tr>
            <tr>
              <th>MID</th>
              <td><input type="text" name="MID" v-model="merchantID" /></td>
            </tr>
            <tr>
              <th>Moid</th>
              <td><input type="text" name="Moid" v-model="moid" /></td>
            </tr>
            <tr>
              <th>BuyerName</th>
              <td><input type="text" name="BuyerName" v-model="buyerName" /></td>
            </tr>
            <tr>
              <th>BuyerEmail</th>
              <td>
                <input type="text" name="BuyerEmail" v-model="buyerEmail" />
              </td>
            </tr>
            <tr>
              <th>BuyerTel</th>
              <td><input type="text" name="BuyerTel" v-model="buyerTel" /></td>
            </tr>
            <tr>
              <th>ReturnURL [Mobile only]</th>
              <td><input type="text" name="ReturnURL" v-model="returnURL" /></td>
            </tr>
            <tr>
              <th>Virtual Account Expiration Date(YYYYMMDD)</th>
              <td><input type="text" name="VbankExpDate" value="" /></td>
            </tr>
    
            <input type="hidden" name="NpLang" value="KO" />
            <!-- EN:English, CN:Chinese, KO:Korean -->
            <input type="hidden" name="GoodsCl" value="1" />
            <!-- products(1), contents(0)) -->
            <input type="hidden" name="TransType" value="0" />
            <!-- USE escrow false(0)/true(1) -->
            <input type="hidden" name="CharSet" value="utf-8" />
            <!-- Return CharSet -->
            <input type="hidden" name="ReqReserved" value="" />
            <!-- mall custom field -->
    
            <!-- DO NOT CHANGE -->
            <input type="hidden" name="EdiDate" v-model="ediDate" />
            <!-- YYYYMMDDHHMISS -->
            <input type="hidden" name="SignData" v-model="hashString" />
            <!-- EncryptData -->
          </table>
          <a href="#" class="btn_blue" @click="nicepayStart">REQUEST</a>
          <a href="#" class="btn_blue" @click="test">test</a>
        </form>
      </body>
    </template>
    
    
    <style scoped>
    </style>

    딱 요정도만 하면 되고
    솔직히 이코드의 

     <form
          name="payForm"
          method="post"
          action="http://localhost:3000/payment/authReq"
          accept-charset="euc-kr"
        >

    이부분이 가장 중요하고 이부분 때문에 가장 애먹었다

    다시한번 코드가 뭔지 모두 어떻게 돌아가는지 알아야 한다는것 절실히 느낀다

    예시코드는 

    <form name="payForm" method="post" action="/authReq" accept-charset="euc-kr">

    이렇게 되어있엇는데 저 action이 보내는 url인걸모르고 그냥 저렇게 써도 되는줄 알았다

    method="post" 가 있는걸 보고 눈치챘어야했는데 사실 그냥 넘긴게 크다

    저기로 url 을 입력하면 해당값들을 알아서 post 요청하면서 값을 인증요청하여 넘겨준다

    그리고 서버에서는 그 값을 가공해서 url에 다시 포스트 요청만하면 되는것이엇고...

    그럼 

    {"ResultCode":"3001","ResultMsg":"ī�� ���� ����","MsgSource":"PG","Amt":"000000001004","MID":"nicepay00m","Moid":"nice_api_test_3.0","BuyerEmail":"happy@day.com","BuyerTel":"00000000000","BuyerName":"������","GoodsName":"���̽���ǰ","TID":"nicepay00m01012403031338384318","AuthCode":"39102125","AuthDate":"240303133839","PayMethod":"CARD","CartData":"","Signature":"3dba0832029f2fabf84b00f9254815e46591988214e3f02963da943b7a932150","MallReserved":"","CardCode":"06","CardName":"����","CardNo":"45184212****6280","CardQuota":"00","CardInterest":"0","AcquCardCode":"06","AcquCardName":"����","CardCl":"0","CcPartCl":"1","CouponAmt":"000000000000","CouponMinAmt":"000000000000","PointAppAmt":"000000000000","ClickpayCl":"","MultiCl":"","MultiCardAcquAmt":"","MultiPointAmt":"","MultiCouponAmt":"","MultiDiscountAmt":"","RcptType":"","RcptTID":"","RcptAuthCode":"","CardType":"01","ApproveCardQuota":"00","PointCl":"0"}

    이렇게 나오는데 3001 이 카드결제 성공 이라는 뜻이다

    지금 nicePay를  vue 그리고 nestJS 로 작성한 예시가 하나도 없던데

    누군가는 도움이 되면 좋겟다.

    아니면 다른사람들에겐 너무 쉬엇을 수 있고..

    댓글

Designed by Tistory.