반응형
⚡️ 개발환경
- Front - React.js
- Back - SpringBoot, Spring Security, JPA
- DB - MySql
- Server - Tomcat 9
- Tool - sts, VScode
- Build - maven
⚡️ 시나리오
- React에서 API서버로 UI출력 및 결제요청 호출
- API 서버로부터 Redirect 받은 정보 Spring 서버로 전달 및 요청
- 전달받은 정보이용 Spring에서 Toss API 서버로 결제 승인 요청
- 반환받은 승인 결과 DB 저장
- 저장된 DB를 React로 응답
- 응답된 결과를 React로 UI 출력
1. 토스페이먼츠 API 키 발급
2. 리액트 root 폴더 아래에 .env 파일 생성 후 클라이언트 키 삽입
( 나는 .env 파일에 클라이언트 키가 인식이 안돼서 하드코딩해서 넣음 )
- 인식이 안될때는 하드코딩
const clientKey = 'test_ck_KNbdOvk5rkOogZa2Qvm4rn07xlzm'; // 하드코딩된 클라이언트 키
const tossPayments = await loadTossPayments(clientKey);
3. 스프링부트 application.properties에 시크릿 키 삽입
결제창 여는 방법
https://www.tosspayments.com/blog/articles/paytech-1
1) SDK ( 이걸로 해볼 예정 )
2) 카드사, 간편결제사 창 바로 열기
⚡️결제 요청 ( 프론트 )
#설치 커맨드
npm install @tosspayments/payment-sdk --save
sdk는 토스페이먼츠 위젯을 불러옴
import { loadTossPayments } from '@tosspayments/payment-sdk';
const token = getAuthToken(); // token (로그인했을때 생성, 아이디별 다른 정보를 보여주기 위함) 값 저장
// Spring Security antMatchers에 url을 넣으면 누구나 접근가능해서 테스트 용도로는 사용가능하지만
// 실제 서비스 이용을 할 때는 antMatchers에 로그인을 제외하고 삭제시키고 로그인시 token값을 받아와 다른
// 페이지 접속이 가능하도록 한다.
const branchId = localStorage.getItem('branchId'); // 저장된 branchId 가져오기
//결제하기
const handlePayment = async () => {
const clientKey = 'test_ck_KNbdOvk5rkOogZa2Qvm4rn07xlzm'; // 하드코딩된 클라이언트 키
const tossPayments = await loadTossPayments(clientKey);
const paymentData = {
amount: totalCostPrice, // 결제 금액
orderId: '1234-4321-0001', // 주문 ID
orderName: `${branchId} 발주`, // 주문명
customerName: `${branchId}` // 고객명
};
// 오더 카트의 모든 상품 정보를 문자열로 변환
const itemsString = encodeURIComponent(JSON.stringify(orderCart));
try {
// 백엔드에 결제 정보 저장 요청
await axios.post(`http://localhost:8090/traders/payment/${branchId}`, paymentData,
// post 방식의 데이터 처리는 headers가 필수!!
{
headers: {
Authorization: `Bearer ${token}`,
"content-type": 'application/json'
}
});
// URL 쿼리 파라미터에 결제 정보를 추가하여 결제 성공 페이지로 리다이렉트
const queryString = new URLSearchParams({
orderId: paymentData.orderId,
amount: paymentData.amount,
customerName: paymentData.customerName,
items: itemsString, // 전체 상품 정보 추가
}).toString();
// 결제 요청
tossPayments.requestPayment('카드', {
...paymentData,
successUrl: `http://localhost:3000/traders/payment/PaymentSuccess?${queryString}`,
failUrl: 'http://localhost:3000/traders/payment/fail'
});
} catch (error) {
console.error('결제 요청 중 오류 발생:', error);
}
};
⚡️결제 처리 ( 백엔드 )
Toss API 서버로 결제 승인 요청하는 로직 필요
⚡️결제 성공 페이지 ( 프론트 ) ( 결제 완료 버튼 클릭시 데이터가 movement DB에 저장 )
import React from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import './PaymentSuccess.css';
import axios from 'axios';
import { getAuthToken } from '../util/auth';
function PaymentSuccess() {
const location = useLocation();
const navigate = useNavigate();
const searchParams = new URLSearchParams(location.search);
const orderId = searchParams.get('orderId');
const amount = searchParams.get('amount');
const customerName = searchParams.get('customerName');
const items = JSON.parse(decodeURIComponent(searchParams.get('items')) || '[]'); // 모든 상품 정보 가져오기
const token = getAuthToken(); // token 값 저장
const handleComplete = async () => {
//async/await 는 비동기(try, catch처럼 순서대로 실행되지 않는 로직)작업할때 많이 씀
// 무브먼트에 저장할 데이터 구성
const movementData = items.map(item => ({
ordercode: orderId, // 주문 ID
branchid: customerName, // 지점명 (branchId로 사용)
gcode: item.goods.gcode, // 상품 코드
movquantity: item.gcount, // 수량
movdate: new Date().toISOString().split('T')[0], // 오늘 날짜 (YYYY-MM-DD 형식)
movstatus: '출고대기' // 고정된 상태
}));
try {
// 무브먼트 DB에 저장하기 위한 POST 요청
const response = await axios.post('http://localhost:8090/traders/movement/ordersave', movementData,
{
// post 방식의 데이터 처리는 headers가 필수!!
headers: {
"content-type": 'application/json',
Authorization: `Bearer ${token}`,
}
});
console.log('저장 완료:', response.data);
// 저장 완료 후 대시보드 페이지로 이동
navigate('/');
} catch (error) {
console.error('저장 중 오류 발생:', error);
alert('저장 중 오류가 발생했습니다. 다시 시도해 주세요.');
}
};
return (
<div className="payment-success-container">
<h1 className="success-title">결제 성공</h1>
<div className="success-details">
<p className="detail">지점명: <span className="highlight">{customerName}</span></p>
<p className="detail">주문 ID: <span className="highlight">{orderId}</span></p>
<p className="detail">결제 금액: <span className="highlight">{amount}원</span></p>
<h2>구매한 상품 목록</h2>
<ul>
{items.map((item, index) => (
<li key={index}>
상품명: {item.goods?.gname} 상품코드: {item.goods.gcode} 가격: {item.goods?.gcostprice.toLocaleString('ko-KR')}원 수량: {item.gcount}개
</li>
))}
</ul>
</div>
<button className="complete-button" onClick={handleComplete}>결제완료</button>
</div>
);
}
export default PaymentSuccess;
App.js에 라우팅 설정
{
path: "/traders/payment/PaymentSuccess",
element: <PaymentSuccess />
}
⚡️결제 성공 후 결제 완료 버튼 클릭시 데이터가 movement DB에 저장 ( 백엔드 )
엔티티, DTO, 레포지토리는 기본
결과
728x90
반응형
'Final Project 신세계아이앤씨' 카테고리의 다른 글
홈페이지 들어가면 달력에 오늘 날짜 보이게하기 (0) | 2024.07.31 |
---|---|
달력 일자 선택시 7일이내의 유통기한 임박상품 불러오기 ( 리액트, 스프링부트 ) (0) | 2024.07.30 |
재고관리 페이지 ( 리액트, 스프링부트 ) (0) | 2024.07.24 |
캘린더 (react, npm, 커스텀) (0) | 2024.07.23 |
Final 기획 (1) | 2024.07.20 |