-
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.zip3.72MBnice 공홈에서도 찾아볼 수 있는 샘플이다
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 로 작성한 예시가 하나도 없던데
누군가는 도움이 되면 좋겟다.
아니면 다른사람들에겐 너무 쉬엇을 수 있고..