스프링부트+jsp로 배달사이트 만들기-40 결제api 사용해서 주문하기(아임포트)

2022. 1. 9. 20:49스프링부트

https://www.iamport.kr/

 

온라인 비즈니스의 모든 결제를 한곳에서, 아임포트

결제의 시작부터 비즈니스의 성장까지 아임포트와 함께하세요

www.iamport.kr

 

아임포트에 회원가입을 하고 관리자 콘솔로 이동합니다

 

 

원하는 PG사를 선택하고 테스트모드를 ON해준 뒤 저장합니다

내 정보에서 가맹점식별코드, REST API키, RESET API secret을 확인 할 수 있습니다

 


결제가 필요한 order폴더의 order.jsp 아래에 아임포트 라이브러리를 추가합니다

<!-- iamport.payment.js -->
<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.8.js"></script>

 

 

order.js에서 기존의 payment 함수를 수정합니다

orderNum은 주문페이지로 이동할때 자바단에서 생성하였었는데 결제시 프론트에서 생성하도록 수정하였습니다

자바단에서 orderNum생성로직을 삭제해줍니다

payMethod가 신용카드일땐 paymentCard()를 호출하게 수정합니다

 

 

// 주문번호 만들기
function createOrderNum(){
	const date = new Date();
	const year = date.getFullYear();
	const month = String(date.getMonth() + 1).padStart(2, "0");
	const day = String(date.getDate()).padStart(2, "0");
	
	let orderNum = year + month + day;
	for(let i=0;i<10;i++) {
		orderNum += Math.floor(Math.random() * 8);	
	}
	return orderNum;
}

 

 

// 카드 결제
function paymentCard(data) {
	// 모바일로 결제시 이동페이지
	const pathName = location.pathname;
	const href = location.href;
	const m_redirect = href.replaceAll(pathName, "");
		
	IMP.init("imp99151903"); 
		
	IMP.request_pay({ // param
		pg: "html5_inicis",
	  	pay_method: data.payMethod,
	  	merchant_uid: data.orderNum,
	  	name: data.name,
	  	amount: data.amount,
	   	buyer_email: "",
	   	buyer_name: "",
	  	buyer_tel: data.phone,
	  	buyer_addr: data.deleveryAddress2 + " " + data.deleveryAddress3,
	  	buyer_postcode: data.deleveryAddress1,
	  	m_redirect_url: m_redirect, 
  	}, 
	function (rsp) { // callback
		if (rsp.success) {
         // 결제 성공 시 로직,
	        data.impUid = rsp.imp_uid;
	        data.merchant_uid = rsp.merchant_uid;
	        paymentComplete(data);  
			
		} else {
          // 결제 실패 시 로직,
		}
	});
}

IMP.init에는 가맹점 식별코드를 적어줍니다

 

 

 

여기까지만 해도 결제는 되지만 html의 음식 가격을 조작했을 경우에도 그 가격 그대로 결제가 되버립니다

그래서 서버와 가격이 맞지 않을때 결제 성공시 응답받은 imp_uid로 결제를 취소해야합니다

 

 

주석 되있던 계산완료 함수를 주석해제 합니다

 

 

OrderInfo클래스에 아임포트 결제번호 주석을 해제합니다

private String impUid = ""; // 아임포트 결제번호 추가

 

OrderController에 결제 후 메서드를 추가합니다

// 카드 결제 성공 후
@PostMapping("/order/payment/complete")
public ResponseEntity<String> paymentComplete(HttpSession session, OrderInfo orderInfo, long totalPrice) throws IOException {

	// 1. 아임포트 API 키와 SECRET키로 토큰을 생성
    
	// 2. 토큰으로 결제 완료된 주문정보를 가져옴
    
    // 3. 로그인하지 않았는데 사용포인트가 0 이상일경우 결제 취소
    
    // 4. 로그인 사용자가 현재포인트보다 사용포인트가 많을 경우 결제 취소
    
    // 5. DB에서 실제 계산되어야 할 가격가져오기
    
    // 6. 결제 완료된 금액과 실제 계산되어야 할 금액이 다를경우 결제 취소
    
    // 7. 결제에러시 결제 취소
    
	


  return new ResponseEntity<>(HttpStatus.OK);
}

1. 아임포트 API 키와 SECRET키로 토큰을 생성
2. 토큰으로 결제 완료된 주문정보를 가져옴
3. 로그인하지 않았는데 사용포인트가 0 이상일경우 결제 취소
4. 로그인 사용자가 현재포인트보다 사용포인트가 많을 경우 결제 취소
5. DB에서 실제 계산되어야 할 가격가져오기
6. 결제 완료된 금액과 실제 계산되어야 할 금액이 다를경우 결제 취소
7. 결제에러시 결제 취소

 

service패키지에 결제를 담당할 PaymentService를 생성합니다

 

application.properties에 REST API키와 SECRET 키를 추가합니다

imp_key=REST API키
imp_secret=SECRET 키

 

PaymentService

public interface PaymentService {

	String getToken() throws IOException;
	
	int paymentInfo(String imp_uid, String access_token);
	
	public void payMentCancle(String access_token, String imp_uid, String amount, String reason);
	
}

 

 

PaymentServiceImp 

	@Service
	public class PaymentServiceImp implements PaymentService {
		
		@Value("${imp_key}")
		private String impKey;

		@Value("${imp_secret}")
		private String impSecret;

		public String getToken() throws IOException {

			HttpsURLConnection conn = null;

			URL url = new URL("https://api.iamport.kr/users/getToken");

			conn = (HttpsURLConnection) url.openConnection();

			conn.setRequestMethod("POST");
			conn.setRequestProperty("Content-type", "application/json");
			conn.setRequestProperty("Accept", "application/json");
			conn.setDoOutput(true);
			JsonObject json = new JsonObject();

			json.addProperty("imp_key", impKey);
			json.addProperty("imp_secret", impSecret);
			
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
			
			bw.write(json.toString());
			bw.flush();
			bw.close();

			BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));

			Gson gson = new Gson();

			String response = gson.fromJson(br.readLine(), Map.class).get("response").toString();
			
			System.out.println(response);

			String token = gson.fromJson(response, Map.class).get("access_token").toString();

			br.close();
			conn.disconnect();

			return token;
		}

		public int paymentInfo(String imp_uid, String access_token) {

			return 0;
		}
		
		
		
		public void payMentCancle(String access_token, String imp_uid, String amount, String reason)  {
			
			
		}

}

 

컨트롤러에서 토큰이 생성되는지 확인해봅니다

@Autowired
private PaymentService paymentService;
String token = paymentService.getToken();
System.out.println("토큰 : " + token);

 

이제 토큰으로 결제 정보를 가져오는 코드를 추가합니다

public int paymentInfo(String imp_uid, String access_token) throws IOException {
    HttpsURLConnection conn = null;

    URL url = new URL("https://api.iamport.kr/payments/" + imp_uid);

    conn = (HttpsURLConnection) url.openConnection();

    conn.setRequestMethod("GET");
    conn.setRequestProperty("Authorization", access_token);
    conn.setDoOutput(true);

    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
    
    Gson gson = new Gson();
    
    Response response = gson.fromJson(br.readLine(), Response.class);
    
    br.close();
    conn.disconnect();
    
    return response.getResponse().getAmount();
}

토큰으로 요청한 결제정보는 {"code":0,"message":null,"response":{"amount":6000, .... } 형태의 중첩 JSON형태입니다

이 JSON에서 필요한 값은 취소할 금액인 amount만 가져오면 됩니다

JSON을 파싱할 클래스 Response와 JsonInfo를 만드는데 PaymentServiceImp클래스 안에서만 사용하기 때문에

PaymentServiceImp 내부에 클래스를 생성합니다

 

 

 

 

각각의 조건에 맞게 코드를 추가합니다

 

// 카드 결제 성공 후
@PostMapping("/order/payment/complete")
public ResponseEntity<String> paymentComplete(HttpSession session, OrderInfo orderInfo, long totalPrice, @AuthenticationPrincipal LoginService user) throws IOException {
    
    String token = paymentService.getToken();
    
    System.out.println("토큰 : " + token);
    // 결제 완료된 금액
    int amount = paymentService.paymentInfo(orderInfo.getImpUid(), token);
    
    try {
        // 주문 시 사용한 포인트
        int usedPoint = orderInfo.getUsedPoint();
        
        if (user != null) {
            int point = user.getUser().getPoint();
            
            // 사용된 포인트가 유저의 포인트보다 많을 때
            if (point < usedPoint) {
                paymentService.payMentCancle(token, orderInfo.getImpUid(), amount, "유저 포인트 오류");
                return new ResponseEntity<String>("유저 포인트 오류", HttpStatus.BAD_REQUEST);
            }

        } else {
            // 로그인 하지않았는데 포인트사용 되었을 때
            if (usedPoint != 0) {
                paymentService.payMentCancle(token, orderInfo.getImpUid(), amount, "비회원 포인트사용 오류");
                return new ResponseEntity<String>("비회원 포인트 사용 오류", HttpStatus.BAD_REQUEST);
            }
        }
        
        CartList cartList = (CartList) session.getAttribute("cartList");
        // 실제 계산 금액 가져오기
        long orderPriceCheck = orderService.orderPriceCheck(cartList)  - usedPoint;
        
        // 계산 된 금액과 실제 금액이 다를 때
        if (orderPriceCheck != amount) {
            paymentService.payMentCancle(token, orderInfo.getImpUid(), amount, "결제 금액 오류");
            return new ResponseEntity<String>("결제 금액 오류, 결제 취소", HttpStatus.BAD_REQUEST);
        }
        
        orderService.order(cartList, orderInfo, user);
        session.removeAttribute("cartList");
        
        return new ResponseEntity<>("주문이 완료되었습니다", HttpStatus.OK);
        
    } catch (Exception e) {
        paymentService.payMentCancle(token, orderInfo.getImpUid(), amount, "결제 에러");
        return new ResponseEntity<String>("결제 에러", HttpStatus.BAD_REQUEST);
    }
    
    
}

 

 

결제 취소 코드를 추가합니다

public void payMentCancle(String access_token, String imp_uid, int amount, String reason) throws IOException  {
		System.out.println("결제 취소");
		
		System.out.println(access_token);
		
		System.out.println(imp_uid);
		
		HttpsURLConnection conn = null;
		URL url = new URL("https://api.iamport.kr/payments/cancel");

		conn = (HttpsURLConnection) url.openConnection();

		conn.setRequestMethod("POST");

		conn.setRequestProperty("Content-type", "application/json");
		conn.setRequestProperty("Accept", "application/json");
		conn.setRequestProperty("Authorization", access_token);

		conn.setDoOutput(true);
		
		JsonObject json = new JsonObject();

		json.addProperty("reason", reason);
		json.addProperty("imp_uid", imp_uid);
		json.addProperty("amount", amount);
		json.addProperty("checksum", amount);

		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));

		bw.write(json.toString());
		bw.flush();
		bw.close();
		
		BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));

		br.close();
		conn.disconnect();
		
		
	}

 

 

정상 결제일때

 

금액을 조작했을때

 

 

관리자 페이지에서도 관리자가 주문거부를 할수있게  OderMapper의 order쿼리에 IMP_UID를 추가합니다