## 목차
1) Iamport?
2) 적용 이유?
3) 프로젝트 활용!
4) 회고
#1 Iamport?
아임포트는 아래와 같이 포트원에서 제공하는 결제 플랫폼 OpenAPI 입니다.
기능을 구현하면서 많은 분들의 블로그도 참고하였지만, 아래 manual을 기반으로 기능을 구현하였습니다.
아임포트(I'mport)는,
PG사 결제모듈에 대한 연동 개발을 진행할 때, 다양한 개발환경에서 보다 쉽고 빠르게 개발할 수 있도록 제공되는 결제 플랫폼 혹은 결제 호스팅 서비스 입니다.
결제모듈연동을 위해 불필요한 수고를 해야하는 개발자의 입장에서 만들어진 서비스이기 때문에, 아쉽지만 구매자는 아임포트를 기반으로 만들어진 사이트인지 그렇지 않은 사이트인지 구분하지 못합니다.
결제 프로세스 도중 카드사가 필요로하는 보안키보드모듈, 방화벽모듈이 있다면 늘 그래왔던 것처럼 내 컴퓨터에 설치해야 하는 것은 똑같다는 것을 의미합니다.
이것은 PG사와 카드사가 요구하는 인증결제 프로세스를 그대로 준수하도록 아임포트가 설계되었다는 것을 의미하기도 하므로, 아임포트가 PG사나 카드사의 backdoor를 찾아 서비스하는 것은 아닌지 걱정하실 필요가 없습니다.
카페24, 메이크샵, 고도몰과 같은 쇼핑몰 호스팅 서비스들처럼 아임포트 서버가 가맹점의 서버를 대신해 결제를 위한 통신과정을 모두 마친 후 그 결과를 기술적인 방법으로 알려줄 뿐입니다.
특징
한글처리방식
UTF-8인코딩을 사용합니다. euc-kr 인코딩 변환에 대한 수고가 필요없습니다.
javascript API
html form submission 보다는 javascript function 을 사용합니다.
PG결제창 호출을 위해 hidden input에 데이터를 전달하여 form submit하는 대신,
<form name="pgForm">
<input type="hidden" name="Amt" value="1000">
<input type="hidden" name="BuyerName" value="홍길동">
<input type="hidden" name="OrderName" value="결제테스트">
</form>
javascript function을 호출하면 됩니다.
IMP.request_pay({
amount : 1000,
buyer_name : '홍길동',
name : '결제테스트'
}, function(response) {
//결제 후 호출되는 callback함수
if ( response.success ) { //결제 성공
console.log(response);
} else {
alert('결제실패 : ' + response.error_msg);
}
})
REST API
결제완료 후 결제 결과를 조회할 수 있도록 인증 과정을 거쳐 REST API가 제공됩니다. 때문에, 사용하시는 Backend 프로그래밍 언어와 무관하게 복잡한 설치 프로그램 필요없이 연동이 가능합니다.
결제 방식
인증 결제
카드정보 입력 및 안심클릭인증 또는 ISP인증 후 결제가 진행되는 일반적인 결제 방식입니다.
인증프로세스 및 결제프로세스에 대한 보다 상세한 안내는 인증결제/background.md를 참고해주세요.
결제 연동에 대한 상세한 매뉴얼은 인증결제를 참고해주세요.
비인증 결제
안심클릭인증, ISP인증과 같은 일반적인 인증 프로세스 대신, 카드번호+유효기간+생년월일+비밀번호 확인을 통한 간략화된 인증 후 결제가 이루어지는 방식입니다.
최초 1회 카드정보 등록 후 매월 자동으로 결제가 이뤄져야 하는 정기구독형태의 과금 등 특수한 경우에 한하여 제공됩니다.
보다 상세한 연동 안내는 빌링키 발급 및 재결제를 참고해주세요.
아래 iamport-manual git readme를 확인하시면 더욱 자세한 내용을 확인 하실 수 있습니다.
https://github.com/iamport/iamport-manual
GitHub - iamport/iamport-manual: 아임포트(iamport) 결제연동을 위한 매뉴얼입니다.
아임포트(iamport) 결제연동을 위한 매뉴얼입니다. Contribute to iamport/iamport-manual development by creating an account on GitHub.
github.com
#2 적용이유?
저희 프로젝트의 경우 판매회원분들이 업로드하는 메뉴들을 일반회원들이 '퀵배송' 또는 '포장' 방식으로 주문 할 수 있도록 기획을 하였습니다. 그래서 주문 정보에서 주문 할 메뉴를 선택하고 수령방식과 수령 정보를 선택 및 입력 한 후에 결제하기를 클릭 시 주문 정보를 가지고 아임포트 카카오페이 모듈을 사용하여 결제하도록 구현을 하였습니다.
이를 통해 일반회원의 경우, 추가적인 전화나 문자로 주문을 하여 소통의 결여나 메뉴 내용의 불일치 등 여러가지 불편한 사항 없이 정확하게 주문을 할 수 있고 판매자회원의 경우, 일반회원이 결제한 주문 정보를 토대로 메뉴 준비를 하여 바쁠 때 주문 과열을 방지하고 원활한 선입선출이 가능하도록 편리한 판매를 제공하기 위해서 결제기능을 필수로 추가하였습니다.
그래서 결제기능을 보다 쉽고 정확하고 빠르게 기능을 구현 할 수 있는 방법에 대해서 찾아보다가 아임포트의 오픈API를 사용하여 기능을 구현 할 수 있는 방법에 대해 알게 되었고, 다양한 PG사와의 테스트 결제도 가능해서 사용하게 되었습니다.
토스페이먼츠, KG이니시스 등 다양한 모듈을 적용해보았지만, 토스페이먼츠의 경우 모듈을 오픈한지 얼마 안되어서 그런지 주문 정보까지는 잘 저장이 되는데 실제로 결제 테스트를 할 때 결제가 이루어지지 않아 배제를 하였고, KG이니시스는 다양한 PG사가 연동되어 있지만, 실제로 결제기능이 작동하는건 카카오페이 뿐이었습니다. 그래서 카카오페이 모듈을 사용해서 QR로 손쉽고 빠르게 결제 가능도록 구현을 하였으며, 다른 모듈에 대해서는 추후 더 연구해보도록 결정하였습니다.
#3 프로젝트 활용!
결제 기능은 아래와 같은 구조로 구동됩니다.
참고하면서 어디에서 기능이 어떻게 이루어지고 왜 이렇게 구조가 짜여지는지 이해하면서 구현하여 훨씬 구현하기 쉬웠습니다.
1. 포트원 회원가입 하기
포트원, 온라인 비즈니스를 위한 통합 결제 솔루션
코드 한 줄로 세상 모든 방식의 결제를 경험해보세요
portone.io
2. 결제연동
메인화면에서 시작하기를 누르면 관리자콘솔로 이동합니다.
후에 좌측 메뉴바에서 '결제 연동'을 클릭!
그리고 내 식별코드와 Key값을 확인해줍니다. Key값은 추후에 yml 파일에 설정해주어 iamport와 server의 요청을 연동해주어 식별하는 역할을 합니다.
그리고 아래와 같이 전자결제 테스트용으로 신청을 해줍니다.
저는 카카오페이 간편결제 모듈을 사용할거라서 아래와 같이 신청해주었습니다.
이렇게 하면 결제 연동을 위한 iamport에서 설정은 완료하였습니다.
더욱 자세한 내용은 아래 portone 가이드를 참조하시면 좋습니다.
https://developers.portone.io/docs/ko/console/guide/connect?v=v1
결제 연동 하기
결제 연동 하기 결제 연동 체계 결제 채널이란: 결제의 객체를 칭하는 명칭으로써 결제 대행사가 발급해준 credential 단위로 이루어 집니다. 해당 결제 채널을 가맹점이 직접 사용하시거나 가맹점
developers.portone.io
https://developers.portone.io/docs/ko/ready/2-pg/pg/kakao?v=v1
카카오페이 설정
developers.portone.io
3. 프로젝트 설정
[Spring Boot]
build.gradle
제 프로젝트는 version이 3.1.4 버전이었고, 아래와 같이 결제모듈을 사용하기위해 repository와 implementation을 설정해주었습니다.
plugins {
id 'java'
id 'java-library'
id 'org.springframework.boot' version '3.1.4'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
// 결제
implementation 'com.github.iamport:iamport-rest-client-java:0.2.23'
application-pay.yml
application-pay.yml 파일에 REST API Key 값과 REST API Secret key값을 입력해줍니다.
iamport:
key: "REST API Key"
secret: "REST API Secret"
application.yml
그리고 application을 실행시킬때 공통으로 호출 가능하도록 아래와 같이 - pay 를 include로 설정해줍니다.
spring:
profiles:
active: local
include:
- auth
- pay
- jwt
mvc:
static-path-pattern: static/**
thymeleaf:
prefix: file:src/main/resources/templates/
web:
resources:
static-locations: file:src/main/resources/static/
cache-period: 0
Pay.class
Pay Entity이며, 결제에 필요한 정보들 입니다.
package bleuauction.bleuauction_be.server.pay.entity;
import bleuauction.bleuauction_be.server.order.entity.Order;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import java.sql.Timestamp;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.CreationTimestamp;
import org.springframework.data.annotation.LastModifiedDate;
@Entity
@Data
@Slf4j
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Table(name = "ba_pay")
public class Pay {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "pay_no")
private Long payNo;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name ="order_no")
private Order orderNo;
@Enumerated(EnumType.STRING)
@Column(name = "pay_type")
private PayType payType;
@NotNull
private Integer payPrice;
@CreationTimestamp
@Column(name = "pay_datetime")
private Timestamp payDatetime;
@LastModifiedDate
@Column(name = "pay_cancel_datetime")
private Timestamp payCancelDatetime;
@Enumerated(EnumType.STRING)
@Column(name = "pay_status")
private PayStatus payStatus;
}
PayStatus.class
결제 상태를 저장합니다.
package bleuauction.bleuauction_be.server.pay.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum PayStatus {
Y("결제완료"),
N("결제취소");
private final String value;
}
PayType.class
결제의 유형을 저장합니다.
package bleuauction.bleuauction_be.server.pay.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum PayType {
C("카드결제"),
L("현장결제"), // 가게 사장님이랑 협의
A("계좌이체"); // 가게 사장님이랑 협의
private final String value;
}
PayException.class
exception 설정
package bleuauction.bleuauction_be.server.pay.exception;
import com.siot.IamportRestClient.exception.IamportResponseException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
@ControllerAdvice
@RestController
public class PayException {
@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<String> handleDataIntegrityViolationException(DataIntegrityViolationException e) {
String message = "값이 제대로 입력되지 않았습니다. (DataIntegrityViolationException)";
return new ResponseEntity<>(message, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(verifyIamportException.class)
public ResponseEntity<String> handleVerifyIamportException(verifyIamportException e) {
return new ResponseEntity<>("실제 결제금액과 서버에서 결제금액이 다릅니다.", HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(RefundAmountIsDifferent.class)
public ResponseEntity<String> handleRefundAmountIsDifferent(RefundAmountIsDifferent e) {
return new ResponseEntity<>("환불가능 금액과 결제했던 금액이 일치하지 않습니다.", HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(IamportResponseException.class)
public ResponseEntity<String> handleIamportResponseException(IamportResponseException e) {
return new ResponseEntity<>("결제관련해서 에러가 발생: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
String message = "서버 에러 입니다. (Exception)";
return new ResponseEntity<>(message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
PayRepository.class
결제정보 관련 저장소입니다.
package bleuauction.bleuauction_be.server.pay.repository;
import bleuauction.bleuauction_be.server.pay.entity.Pay;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PayRepository extends JpaRepository<Pay, Long> {
Optional<Pay> findById(Long payNo);
List<Pay> findAll();
Optional<Pay> findBypayNo(Long payNo);
}
PayService.class
결제 관련 비즈니스로직
package bleuauction.bleuauction_be.server.pay.service;
import bleuauction.bleuauction_be.server.order.entity.Order;
import bleuauction.bleuauction_be.server.order.entity.OrderStatus;
import bleuauction.bleuauction_be.server.order.repository.OrderRepository;
import bleuauction.bleuauction_be.server.pay.dto.PayInsertRequest;
import bleuauction.bleuauction_be.server.pay.entity.Pay;
import bleuauction.bleuauction_be.server.pay.repository.PayRepository;
import com.amazonaws.services.kms.model.NotFoundException;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional
public class PayService {
private final PayRepository payRepository;
private final OrderRepository orderRepository;
public List<Pay> selectPayList() {
return payRepository.findAll();
}
public Pay insert(PayInsertRequest request, Long orderNo) throws Exception {
Optional<Order> optionalOrder = Optional.ofNullable(orderRepository.findOne(orderNo));
Order order = optionalOrder.orElseThrow(() -> new NotFoundException("Order not found with ID: " + orderNo));
if (request.getOrderStatus() != OrderStatus.N) {
throw new IllegalAccessException("주문을 완료해야 결제가 가능합니다.");
}
Pay pay = request.getPayEntity(order);
pay.setPayDatetime(request.getPayDatetime());
pay.setPayCancelDatetime(request.getPayCancelDatetime());
return payRepository.save(pay);
}
}
PayRequest.class
package bleuauction.bleuauction_be.server.pay.dto;
import bleuauction.bleuauction_be.server.order.entity.Order;
import bleuauction.bleuauction_be.server.order.entity.OrderStatus;
import bleuauction.bleuauction_be.server.order.entity.OrderType;
import bleuauction.bleuauction_be.server.pay.entity.Pay;
import bleuauction.bleuauction_be.server.pay.entity.PayStatus;
import bleuauction.bleuauction_be.server.pay.entity.PayType;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.validation.constraints.NotNull;
import java.sql.Timestamp;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class PayInsertRequest {
private PayType payType = PayType.C;
private OrderStatus orderStatus = OrderStatus.Y;
// private Long payNo;
private Long orderNo;
@Enumerated(EnumType.STRING)
private OrderType orderType; // Q:퀵배송, T:포장
private int orderPrice;
@NotNull(message = "결제금액을 입력해주세요.")
private Integer payPrice;
// TODO : payStatus 확인하기
@NotNull
private PayStatus payStatus;
private Timestamp payDatetime;
private Timestamp payCancelDatetime;
public Pay getPayEntity(Order inserOrderEntity) {
return Pay.builder()
.orderNo(inserOrderEntity)
.payType(PayType.C)
.payPrice(this.payPrice)
.payStatus(this.payStatus)
.payDatetime(this.payDatetime)
.payCancelDatetime(this.payCancelDatetime)
.build();
}
}
PayController.class
package bleuauction.bleuauction_be.server.pay.controller;
import bleuauction.bleuauction_be.server.order.entity.Order;
import bleuauction.bleuauction_be.server.order.service.OrderService;
import bleuauction.bleuauction_be.server.pay.dto.PayInsertRequest;
import bleuauction.bleuauction_be.server.pay.entity.Pay;
import bleuauction.bleuauction_be.server.pay.repository.PayRepository;
import bleuauction.bleuauction_be.server.util.CreateJwt;
import com.siot.IamportRestClient.IamportClient;
import com.siot.IamportRestClient.exception.IamportResponseException;
import com.siot.IamportRestClient.response.IamportResponse;
import com.siot.IamportRestClient.response.Payment;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/api/pay")
public class PayController {
@Value("${iamport.key}")
private String restApiKey;
@Value("${iamport.secret}")
private String restApiSecret;
private final OrderService orderService;
private final PayRepository payRepository;
private IamportClient iamportClient;
private final CreateJwt createJwt;
public PayController(OrderService orderService, PayRepository payRepository, @Value("${iamport.key}") String restApiKey, @Value("${iamport.secret}") String restApiSecret,
CreateJwt createJwt) {
this.orderService = orderService;
this.payRepository = payRepository;
this.restApiKey = restApiKey;
this.restApiSecret = restApiSecret;
this.createJwt = createJwt;
log.info("Rest API Key: {}, Rest API Secret: {}", restApiKey, restApiSecret);
}
@PostMapping("/createPayment")
public ResponseEntity<?> createPayment(@RequestHeader("Authorization") String authorizationHeader,
@RequestBody PayInsertRequest payInsertRequest) {
ResponseEntity<?> verificationResult = createJwt.verifyAccessToken(
authorizationHeader,
createJwt);
if (verificationResult != null) {
return verificationResult;
}
Long orderNo = payInsertRequest.getOrderNo();
Optional<Order> orderUser = Optional.ofNullable(orderService.findOne(orderNo));
try {
Order order = orderService.findOne(payInsertRequest.getOrderNo());
// Optional<Order> orderOptional = orderService.getOrderById(payInsertRequest.getOrderNo());
if (order == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
Pay pay = payInsertRequest.getPayEntity(order);
Pay savedPay = payRepository.save(pay);
return ResponseEntity.ok(savedPay);
} catch (Exception e) {
log.error("Error creating payment: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/{payNo}")
public ResponseEntity<Object> detail (@PathVariable Long payNo) throws Exception {
Optional<Pay> payOptional = payRepository.findBypayNo(payNo);
if (payOptional.isPresent()) {
Pay pay = payOptional.get();
return ResponseEntity.ok().body(pay);
} else {
return ResponseEntity.notFound().build();
}
}
@PostConstruct
public void init() {
this.iamportClient = new IamportClient(restApiKey, restApiSecret);
}
@PostMapping("/verifyIamport/{imp_uid}")
public IamportResponse<Payment> paymentByImpUid (@PathVariable("imp_uid") String imp_uid) throws
IamportResponseException, IOException {
return iamportClient.paymentByImpUid(imp_uid);
}
}
더욱 자세한 코드리뷰는 아래 github를 참조해주세요
[React]
React 작업은 프론트담당자가 해주었는데, 코드 정리를 간단히 해보겠습니다.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import {getAccessToken, sendAxiosRequest} from '../utility/common';
const Payment = () => {
useEffect(() => {
const jquery = document.createElement("script");
jquery.src = "http://code.jquery.com/jquery-1.12.4.min.js";
const iamport = document.createElement("script");
iamport.src = "https://cdn.iamport.kr/v1/iamport.js";
document.head.appendChild(jquery);
document.head.appendChild(iamport);
return () => {
document.head.removeChild(jquery);
document.head.removeChild(iamport);
};
}, []);
const [accessToken, setAccessToken] = useState(getAccessToken('a'));
const [member, setMember] = useState(null);
const [pay, setPay] = useState(null);
const [order, setOrder] = useState(null);
useEffect(() => {
}, []);
const requestPay = () => {
console.log('memberState', member);
console.log('orderState', order);
const { IMP } = window;
const buyerEmail = member ? member.memberEmail : '';
const buyerName = member ? member.memberName : '';
const buyerTel = member ? member.memberPhone : '';
const name = order ? order.orderNo : '';
const buyerAddr = order ? order.resipientAddr : '';
const buyerPostcode = order ? order.resipientZipcode : '';
const amount = order.orderPrice;
IMP.init('imp11340204');
IMP.request_pay({
pg: 'kakaopay.TC0ONETIME',
pay_method: 'card',
merchant_uid: new Date().getTime(),
name: name,
amount: amount,
buyer_email: buyerEmail,
buyer_name: buyerName,
buyer_tel: buyerTel,
buyer_addr: buyerAddr,
buyer_postcode: buyerPostcode,
}, async (rsp) => {
console.log('rsp: ', rsp);
try {
const { data } = await axios.post('/api/pay/verifyIamport/' + rsp.imp_uid);
if (rsp.paid_amount === amount) {
alert('결제 성공!');
const testPay = {
orderNo: order.orderNo,
payPrice: amount,
payStatus: rsp.success ? 'Y' : 'N'
}
console.log('testPay.payStatus: ', testPay.payStatus);
axios.post('/api/pay/createPayment', testPay, {
headers: {
'Content-Type': 'application/json',
},
})
.then(response => {
console.log('Pay data:', response.data);
setPay(response.data);
})
.catch(error => {
console.error('Error fetching pay data:', error);
});
} else if (rsp.paid_amount == amount) {
alert('결제 성공?');
} else {
alert('결제 실패?');
}
} catch (error) {
console.error('Error while verifying payment:', error);
alert('결제 실패');
}
});
};
return (
<div>
<button onClick={requestPay}>결제하기</button>
</div>
);
};
export default Payment;
1. 라이브러리 및 모듈 Import:
React, useEffect, useState: React에서 제공하는 기본적인 모듈과 훅을 가져옵니다.
axios: HTTP 요청을 쉽게 처리하기 위한 라이브러리입니다.
getAccessToken, sendAxiosRequest: custom utility 함수로, 아마도 API 통신 및 토큰 관리를 위한 함수일 것입니다.
2. 외부 스크립트 로드:
useEffect 훅을 사용하여 컴포넌트가 마운트될 때 외부 라이브러리(jQuery 및 Iamport)를 동적으로 로드합니다.
3. 상태(State) 관리:
accessToken: getAccessToken 함수를 통해 초기화되는 상태 변수입니다.
member, pay, order: API로부터 받은 데이터를 저장하는 상태 변수들입니다.
4. API 데이터 Fetching:
useEffect 훅을 사용하여 초기 렌더링 시에 API를 통해 member와 order 데이터를 가져오는데, 주석 처리되어 있습니다.
5. 결제 요청 처리 (requestPay 함수):
IMP (아임포트)의 스크립트를 초기화하고, 결제 정보를 설정한 후, IMP.request_pay를 호출하여 결제를 요청합니다.
결제 완료 후, 서버로 결제 정보를 검증하기 위해 /api/pay/verifyIamport/ 엔드포인트에 POST 요청을 보냅니다.
결제가 성공하면 사용자에게 알림을 주고, /api/pay/createPayment 엔드포인트에 결제 정보를 전송하여 서버에 결제 데이터를 생성합니다.
6. 컴포넌트 렌더링:
"결제하기" 버튼을 렌더링하며, 클릭 시 requestPay 함수가 실행됩니다.
더욱 자세한 코드 내용은 아래 github를 참조부탁드립니다.
그리고 아래 iamport-manual github도 도움이 많이 되었습니다.
https://github.com/iamport/iamport-manual
GitHub - iamport/iamport-manual: 아임포트(iamport) 결제연동을 위한 매뉴얼입니다.
아임포트(iamport) 결제연동을 위한 매뉴얼입니다. Contribute to iamport/iamport-manual development by creating an account on GitHub.
github.com
#4 회고
먼저, 저희는 spring 환경과 react환경으로 나누어져 있기 때문에 react에서 어떻게 동적으로 구동이 되는지 파악하고 진행하는 것도 필요했습니다. 처음에는 spring 서버에서만 개발을 하고 테스트를 하고 싶은데 react 서버에서 테스트를 하려면 react에 대해서 공부가 미숙해서 어려웠습니다. 그래서 백엔드 개발자를 꿈꾼다해서 프론트에서 어떻게 구동이 되고 테스트 코드를 구현 할 수 있을 정도로 공부를 더욱 해야겠다는 다짐을 했습니다.
다음으로는 spring에서 개발을 하면서 위와 같이 보면 controller에도 비즈니스로직이 구현되어 있습니다.
코드의 유지, 보수에 있어서는 비즈니스로직에 대해서는 Service에 코드를 구현하고, controller는 단지 http 엔드포인트를 요청 하는 역할로만 나누어져야 훨씬 기능적으로나 유지,보수적으로 관리하기가 쉬울 것 같다고 생각했습니다.
마지막으로는 시간이 없어서 기능을 구현하는데만 급급해서 Test 프레임워크인 Junit5를 사용해서 Test를 하면서 기능적으로 오류가 없는지, 리팩토링을 해도 정상적으로 작동하는지에 대한 테스트를 못해본 것이 아쉬웠습니다. 그래서 기회가 된다면 테스트코드를 작성해서 유닛, 기능, 통합 테스트 등에서 행하여 로직의 신뢰성을 높일 수 있게 해보아야겠습니다.
전체적인 프로젝트 내용은 아래 github를 참고바랍니다.
https://github.com/orgs/NC7-BleuAuction/repositories
NC7-BleuAuction
NC7-BleuAuction has 3 repositories available. Follow their code on GitHub.
github.com
프로젝트 domian 주소 : http://bleuauction.co.kr/
Bleu Auction
bleuauction.co.kr
'Project > BleuAuction' 카테고리의 다른 글
[Spring Security][React]CORS(Cross-Origin-Resource-Sharing) 프로젝트 적용 (1) | 2023.11.04 |
---|