[스프링 + 포트원] 스프링으로 포트원 사용해서 결제 구현 하는 방법(Java, Spring Boot, JPA, PortOne)
![](https://blog.kakaocdn.net/dn/QLkWg/btsiyU8mfOc/LLJUNAmJKYUdf2HU6a3uq1/img.png)
깃허브 링크
포스팅 동기
쇼핑몰을 개발하면 결제 부분에서 어떻게 해야할지 막막한 개발자들이 있을 것 입니다.
저 또한 그랬습니다. 😭
결제를 구현하기 위해서는 각 PG사에서 제공하는 API를 사용하여 개발을 진행해야하는데,
만만한 작업이 아니라고 생각합니다…
이러한 문제를 해결하고자 포트원이라는 업체가 등장했습니다.
그런데…. 이마저도 막상 개발하다보면 어려움을 느낍니다…
그리고 구글링을 하다보면 대부분 javascript
를 사용하여 구현한 예제들만 있고,Spring
을 사용한 예제는 별로 없는 것 같더군요.. ㅎㅎ
저는 이러한 답답함에 조금이라도 도움을 드리고 싶어 글을 작성하게 되었습니다.^^
대한민국 결제 흐름
먼저 우리나라의 결제 흐름에 대해서 이해하셔야 개발하는데 수월 합니다.!!
우리나라의 경우 해외와 다르게 일부 PG사를 제외하고는 카드정보를 저장할 수 없도록 규정되어 있습니다.
그래서 카드정보가 가맹점 서버, PG사 서버를 직접 거쳐가지 않도록 구성하고 있습니다.
![](https://blog.kakaocdn.net/dn/baE04K/btsitUIATHi/Z7trhncDzD3EizwqXCyHn0/img.png)
- 구매자 브라우저에서 카드사 서버로 카드 정보 전달
- 카드사 서버가 구매자 브라우저로 카드 인증 결과 회신
- 구매자 브라우저가 가맹점 서버로 결제 요청
- 가맹점 서버가 계약한 PG사 서버로 결제 요청
- PG사 서버가 카드사 서버로 결제 승인 요청
- 카드사 서버가 PG사 서버로 결제 승인 응답
- PG사 서버가 가맹점 서버로 결제 결과 응답
- 가맹점 서버가 구매자 브라우저로 결제 요청 결과 응답
참고: 대한민국 결제 흐름
포트원 결제 흐름
![](https://blog.kakaocdn.net/dn/bpIvSG/btsiuKk7wjZ/LqQ2c8ofTNutL4NoBKP6Z1/img.png)
가맹점 서버가 해야할 일은 결제 내역을 검증하는 역할을 하면 됩니다.
결제를 처리하는 곳은 포트원과 PG사라고 이해하시면 될것 같습니다.
프로젝트 설명
여러분은 💵 1달러샵이라는 쇼핑몰에 접속하였습니다.!
1달러샵은 1달러를 주문하고 결제할 수 있는 쇼핑몰(?) 입니다.
1달러샵의 흐름은 아래와 같습니다.
https://youtube.com/shorts/QBkIJi62zrs?feature=share
흐름
1. 1달러샵에 접속
2. 주문하기 클릭
2-1. 이때 자동으로 회원가입이 됩니다.
2-2. 2-1에서 가입한 회원이 자동으로 상품을 주문합니다.
3. 결제하기 클릭
4. PG사의 결제창 진입
5. 결제 완료
참고
결제 모듈에 집중할 수 있도록 프로젝트는 최대한 간단하게 구성하였습니다. 😃
프로젝트 생성
프로젝트 설정
![](https://blog.kakaocdn.net/dn/IrzmR/btsiuMJ0K4P/i85jk2cwx6vhSiyDmJTDP1/img.png)
라이브러리 설정
![](https://blog.kakaocdn.net/dn/bIxRWf/btsiuJT3tl9/MwXbS7kDkbozdpmlM91TmK/img.png)
Gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'seaung'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
// 아임포트 관련
maven {url 'https://jitpack.io'}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// 아임포트 관련 //
// https://mvnrepository.com/artifact/com.github.iamport/iamport-rest-client-java
implementation group: 'com.github.iamport', name: 'iamport-rest-client-java', version: '0.2.22'
// https://mvnrepository.com/artifact/com.squareup.retrofit2/adapter-rxjava2
implementation group: 'com.squareup.retrofit2', name: 'adapter-rxjava2', version: '2.9.0'
// https://mvnrepository.com/artifact/com.google.code.gson/gson
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.3'
// https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-gson
implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.3.0'
}
tasks.named('test') {
useJUnitPlatform()
}
외부 라이브러리를 확인하면 아래와 같습니다.
![](https://blog.kakaocdn.net/dn/IG4jl/btsis7hlPNP/TjlR6ZaL9gA8AwWpMjBV7K/img.png)
참고: GitHub - iamport/iamport-rest-client-java: JAVA사용자를 위한 아임포트 REST API 연동 모듈입니다
yml
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:onedoller-shop
username: sa
password:
jpa:
show-sql: true
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 1000
open-in-view: false
# show_sql: true
h2:
console:
enabled: true
path: /h2-console
DB
![](https://blog.kakaocdn.net/dn/YoYld/btsisPVk5El/0p2NWiUPXQPbhjrLwMVLZk/img.png)
PortOne 설정
PG사 선택
PG사는 KG 이니시스를 사용했습니다.
![](https://blog.kakaocdn.net/dn/v3Qqn/btsiyWE6LMf/80kNcHvqLuNjIpMjtI2Po0/img.png)
![](https://blog.kakaocdn.net/dn/cVfuGs/btsiuAQnoku/kKqfWQbHtxbfflohT2tKg0/img.png)
환경변수
구현에 필요한 포트원 환경변수 입니다.
![](https://blog.kakaocdn.net/dn/Z3e23/btsis8Hk1Gp/DSWqm3u3eWZ7mkhKc4NNI1/img.png)
![](https://blog.kakaocdn.net/dn/bvmvj5/btsizc80wep/bLEdUPetAyDTXjQyvRnUX1/img.png)
Config
@Configuration
public class AppConfig {
String apiKey = "REST API Key를 입력합니다.";
String secretKey = "REST API Secret를 입력합니다.";
@Bean
public IamportClient iamportClient() {
return new IamportClient(apiKey, secretKey);
}
}
Entity
Member
@Entity
@Getter
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String address;
@Builder
public Member(String username, String email, String address) {
this.username = username;
this.email = email;
this.address = address;
}
}
Order
@Entity
@Getter
@Table(name = "orders")
@NoArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long price;
private String itemName;
private String orderUid; // 주문 번호
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "payment_id")
private Payment payment;
@Builder
public Order(Long price, String itemName, String orderUid, Member member, Payment payment) {
this.price = price;
this.itemName = itemName;
this.orderUid = orderUid;
this.member = member;
this.payment = payment;
}
}
Payment
@Entity
@Getter
@NoArgsConstructor
public class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long price;
private PaymentStatus status;
private String paymentUid; // 결제 고유 번호
@Builder
public Payment(Long price, PaymentStatus status) {
this.price = price;
this.status = status;
}
public void changePaymentBySuccess(PaymentStatus status, String paymentUid) {
this.status = status;
this.paymentUid = paymentUid;
}
}
PaymentStatus
public enum PaymentStatus {
OK,
READY,
CANCEL
}
Repository
MemberRepository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
OrderRepository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("select o from Order o" +
" left join fetch o.payment p" +
" left join fetch o.member m" +
" where o.orderUid = :orderUid")
Optional<Order> findOrderAndPaymentAndMember(String orderUid);
@Query("select o from Order o" +
" left join fetch o.payment p" +
" where o.orderUid = :orderUid")
Optional<Order> findOrderAndPayment(String orderUid);
}
PaymentRepository
public interface PaymentRepository extends JpaRepository<Payment, Long> {
}
Service
MemberService
주문하기 버튼을 누르면 자동으로 회원을 가입시키는 로직
public interface MemberService {
Member autoRegister(); // 자동 회원 가입
}
MemberServiceImpl
@Service
@Transactional
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
// 회원 자동 생성
@Override
public Member autoRegister() {
Member member = Member.builder()
.username(UUID.randomUUID().toString())
.email("example@example.com")
.address("서울특별시 서초구 역삼동")
.build();
return memberRepository.save(member);
}
}
OrderService
주문하기 버튼을 클릭하면 자동으로 상품을 주문한다.
public interface OrderService {
Order autoOrder(Member member); // 자동 주문
}
OrderServiceImpl
@Service
@Transactional
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final PaymentRepository paymentRepository;
@Override
public Order autoOrder(Member member) {
// 임시 결제내역 생성
Payment payment = Payment.builder()
.price(1000L)
.status(PaymentStatus.READY)
.build();
paymentRepository.save(payment);
// 주문 생성
Order order = Order.builder()
.member(member)
.price(1000L)
.itemName("1달러샵 상품")
.orderUid(UUID.randomUUID().toString())
.payment(payment)
.build();
return orderRepository.save(order);
}
}
PaymentService
뷰로 전달할 결제 요청 데이터를 조회하는 메서드와 결제를 검증하는 메서드 정의했습니다.
public interface PaymentService {
// 결제 요청 데이터 조회
RequestPayDto findRequestDto(String orderUid);
// 결제(콜백)
IamportResponse<Payment> paymentByCallback(PaymentCallbackRequest request);
}
PaymentServiceImpl
@Service
@Transactional
@RequiredArgsConstructor
public class PaymentServiceImpl implements PaymentService {
private final OrderRepository orderRepository;
private final PaymentRepository paymentRepository;
private final IamportClient iamportClient;
@Override
public RequestPayDto findRequestDto(String orderUid) {
Order order = orderRepository.findOrderAndPaymentAndMember(orderUid)
.orElseThrow(() -> new IllegalArgumentException("주문이 없습니다."));
return RequestPayDto.builder()
.buyerName(order.getMember().getUsername())
.buyerEmail(order.getMember().getEmail())
.buyerAddress(order.getMember().getAddress())
.paymentPrice(order.getPayment().getPrice())
.itemName(order.getItemName())
.orderUid(order.getOrderUid())
.build();
}
@Override
public IamportResponse<Payment> paymentByCallback(PaymentCallbackRequest request) {
try {
// 결제 단건 조회(아임포트)
IamportResponse<Payment> iamportResponse = iamportClient.paymentByImpUid(request.getPaymentUid());
// 주문내역 조회
Order order = orderRepository.findOrderAndPayment(request.getOrderUid())
.orElseThrow(() -> new IllegalArgumentException("주문 내역이 없습니다."));
// 결제 완료가 아니면
if(!iamportResponse.getResponse().getStatus().equals("paid")) {
// 주문, 결제 삭제
orderRepository.delete(order);
paymentRepository.delete(order.getPayment());
throw new RuntimeException("결제 미완료");
}
// DB에 저장된 결제 금액
Long price = order.getPayment().getPrice();
// 실 결제 금액
int iamportPrice = iamportResponse.getResponse().getAmount().intValue();
// 결제 금액 검증
if(iamportPrice != price) {
// 주문, 결제 삭제
orderRepository.delete(order);
paymentRepository.delete(order.getPayment());
// 결제금액 위변조로 의심되는 결제금액을 취소(아임포트)
iamportClient.cancelPaymentByImpUid(new CancelData(iamportResponse.getResponse().getImpUid(), true, new BigDecimal(iamportPrice)));
throw new RuntimeException("결제금액 위변조 의심");
}
// 결제 상태 변경
order.getPayment().changePaymentBySuccess(PaymentStatus.OK, iamportResponse.getResponse().getImpUid());
return iamportResponse;
} catch (IamportResponseException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Dto
RequestPayDtoView
로 전달할 결제 관련 데이터 입니다.
@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class RequestPayDto {
private String orderUid;
private String itemName;
private String buyerName;
private Long paymentPrice;
private String buyerEmail;
private String buyerAddress;
@Builder
public RequestPayDto(String orderUid, String itemName, String buyerName, Long paymentPrice, String buyerEmail, String buyerAddress) {
this.orderUid = orderUid;
this.itemName = itemName;
this.buyerName = buyerName;
this.paymentPrice = paymentPrice;
this.buyerEmail = buyerEmail;
this.buyerAddress = buyerAddress;
}
}
PaymentCallbackRequest
결제가 이루어진 후 서버가 전달받는 데이터 입니다.
@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class PaymentCallbackRequest {
private String paymentUid; // 결제 고유 번호
private String orderUid; // 주문 고유 번호
}
MemberRequest
@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class MemberRequest {
private String username;
private String email;
private String address;
}
Controller
HomeController
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
}
OrderController
@Controller
@RequiredArgsConstructor
public class OrderController {
private final MemberService memberService;
private final OrderService orderService;
@GetMapping("/order")
public String order(@RequestParam(name = "message", required = false) String message,
@RequestParam(name = "orderUid", required = false) String id,
Model model) {
model.addAttribute("message", message);
model.addAttribute("orderUid", id);
return "order";
}
@PostMapping("/order")
public String autoOrder() {
Member member = memberService.autoRegister();
Order order = orderService.autoOrder(member);
String message = "주문 실패";
if(order != null) {
message = "주문 성공";
}
String encode = URLEncoder.encode(message, StandardCharsets.UTF_8);
return "redirect:/order?message="+encode+"&orderUid="+order.getOrderUid();
}
}
PaymentController
@Slf4j
@Controller
@RequiredArgsConstructor
public class PaymentController {
private final PaymentService paymentService;
@GetMapping("/payment/{id}")
public String paymentPage(@PathVariable(name = "id", required = false) Long id,
Model model) {
RequestPayDto requestDto = paymentService.findRequestDto(id);
model.addAttribute("requestDto", requestDto);
return "payment";
}
@ResponseBody
@PostMapping("/payment")
public ResponseEntity<IamportResponse<Payment>> validationPayment(@RequestBody PaymentCallbackRequest request) {
IamportResponse<Payment> iamportResponse = paymentService.paymentByCallback(request);
log.info("결제 응답={}", iamportResponse.getResponse().toString());
return new ResponseEntity<>(iamportResponse, HttpStatus.OK);
}
@GetMapping("/success-payment")
public String successPaymentPage() {
return "success-payment";
}
@GetMapping("/fail-payment")
public String failPaymentPage() {
return "fail-payment";
}
}
View
home
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>1달러샵</title>
</head>
<body>
<h1>1달러샵에 오신걸을 환영합니다. ^^</h1>
<a href="/order">주문페이지로 이동</a>
</body>
</html>
order
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>1달러샵</title>
</head>
<body>
<form method="post">
<h1>주문 페이지</h1>
<div>
<input type="submit" value="주문하기">
</div>
<div style="margin-top: 20px">
<a th:href="@{/payment/{orderUid} (orderUid=${orderUid})}">결제 페이지로 이동</a>
</div>
<p th:text="${message}"></p>
</form>
</body>
</html>
payment
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>1달러샵</title>
<script src="https://cdn.iamport.kr/v1/iamport.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script>
var IMP = window.IMP;
IMP.init("가맹점식별코드를 입력합니다.");
function requestPay() {
var orderUid = '[[${requestDto.orderUid}]]';
var itemName = '[[${requestDto.itemName}]]';
var paymentPrice = [[${requestDto.paymentPrice}]];
var buyerName = '[[${requestDto.buyerName}]]';
var buyerEmail = '[[${requestDto.buyerEmail}]]';
var buyerAddress = '[[${requestDto.buyerAddress}]]';
IMP.request_pay({
pg : 'html5_inicis.INIpayTest',
pay_method : 'card',
merchant_uid: orderUid, // 주문 번호
name : itemName, // 상품 이름
amount : paymentPrice, // 상품 가격
buyer_email : buyerEmail, // 구매자 이메일
buyer_name : buyerName, // 구매자 이름
buyer_tel : '010-1234-5678', // 임의의 값
buyer_addr : buyerAddress, // 구매자 주소
buyer_postcode : '123-456', // 임의의 값
},
function(rsp) {
if (rsp.success) {
alert('call back!!: ' + JSON.stringify(rsp));
// 결제 성공 시: 결제 승인 또는 가상계좌 발급에 성공한 경우
// jQuery로 HTTP 요청
jQuery.ajax({
url: "/payment",
method: "POST",
headers: {"Content-Type": "application/json"},
data: JSON.stringify({
"payment_uid": rsp.imp_uid, // 결제 고유번호
"order_uid": rsp.merchant_uid // 주문번호
})
}).done(function (response) {
console.log(response);
// 가맹점 서버 결제 API 성공시 로직
//alert('Please, Check your payment result page!!' + rsp);
alert('결제 완료!' + rsp);
window.location.href = "/success-payment";
})
} else {
// alert("success? "+ rsp.success+ ", 결제에 실패하였습니다. 에러 내용: " + JSON.stringify(rsp));
alert('결제 실패!' + rsp);
window.location.href = "/fail-payment";
}
});
}
</script>
</head>
<body>
<h1>결제 페이지</h1>
<button th:with="requestDto = ${requestDto}" onclick="requestPay()">
결제하기
</button>
</body>
</html>
fail-payment
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1달러샵</title>
</head>
<body>
<h1>결제실패</h1>
<a href="/">홈으로 이동</a>
</body>
</html>
success-payment
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1달러샵</title>
</head>
<body>
<h1>결제성공</h1>
<a href="/">홈으로 이동</a>
</body>
</html>
실행
홈
![](https://blog.kakaocdn.net/dn/b182s9/btsisqasMVU/XixQZi1PV9iq5D73juQ0J0/img.png)
주문페이지
주문페이지로 이동 합니다.
![](https://blog.kakaocdn.net/dn/xOsn0/btsiurTErQs/5FkDFViaysVvYfK0jIMl00/img.png)
주문하기
![](https://blog.kakaocdn.net/dn/cVvFRW/btsiwk0xOxy/T9BngUGlMbYQlkdwfEGlA1/img.png)
서버를 보면 아래와 같은 쿼리가 발생한것을 확인할 수 있습니다.
Hibernate:
insert
into
member
(address,email,username,id)
values
(?,?,?,default)
Hibernate:
insert
into
payment
(payment_uid,price,status,id)
values
(?,?,?,default)
Hibernate:
insert
into
orders
(item_name,member_id,order_uid,payment_id,price,id)
values
(?,?,?,?,?,default)
H2 데이터베이스를 확인하면 데이터가 잘 들어간 것을 확인할 수 있습니다.
![](https://blog.kakaocdn.net/dn/bMGN5p/btsisOIUu5i/vMPwnR6yaMwM0zjXwxVyDk/img.png)
결제 페이지
결제 페이지로 이동합니다.
![](https://blog.kakaocdn.net/dn/bJCNmY/btsisqasQgt/j6iDHJtQulf4rKrJuxgmUk/img.png)
결제하기
결제하기를 클릭합니다.
KG 이니시스 결제창이 생기는 것을 확인할 수 있습니다.
![](https://blog.kakaocdn.net/dn/58hqc/btsiuNhQ5ub/tgdMPSIg4kkc1VACpt6WKk/img.png)
이용약관에 동의하고 결제를 원하는 카드를 선택하고 다음을 클릭합니다.
![](https://blog.kakaocdn.net/dn/cMNOgh/btsis1urXXJ/UlCbAsqdfE1lFBkK7GT9r0/img.png)
결제 버튼을 클릭합니다.
![](https://blog.kakaocdn.net/dn/EagPY/btsiusdYDdM/5cuiV9HFTO0ywc1xmYrIX1/img.png)
포트원에서 보내준 결제 관련 데이터 입니다.
![](https://blog.kakaocdn.net/dn/vOC9k/btsisTp7S4i/iwGtbV6OMdpKgFtS4o7hs0/img.png)
![](https://blog.kakaocdn.net/dn/Pyo6P/btsiuLdgchT/viusZFJXw0hVg9WTSOxIW0/img.png)
결제가 완료되면 결제 성공 페이지로 이동 합니다.
![](https://blog.kakaocdn.net/dn/8Xohr/btsivRYoXff/v21xq0KsQVYqi7ltUYhzAk/img.png)
서버에 아래와 같은 쿼리가 발생한 것을 확인할 수 있습니다.
Hibernate:
select
o1_0.id,
o1_0.item_name,
m1_0.id,
m1_0.address,
m1_0.email,
m1_0.username,
o1_0.order_uid,
p1_0.id,
p1_0.payment_uid,
p1_0.price,
p1_0.status,
o1_0.price
from
orders o1_0
left join
payment p1_0
on p1_0.id=o1_0.payment_id
left join
member m1_0
on m1_0.id=o1_0.member_id
where
o1_0.order_uid=?
Hibernate:
select
o1_0.id,
o1_0.item_name,
o1_0.member_id,
o1_0.order_uid,
p1_0.id,
p1_0.payment_uid,
p1_0.price,
p1_0.status,
o1_0.price
from
orders o1_0
left join
payment p1_0
on p1_0.id=o1_0.payment_id
where
o1_0.order_uid=?
Hibernate:
update
payment
set
payment_uid=?,
price=?,
status=?
where
id=?
payment_uid
에 결제 고유 번호가 저장된 것을 확인할 수 있습니다. (imp_406743040110
)
![](https://blog.kakaocdn.net/dn/1CKnr/btsiu0H1Fi7/1ZKscATxoR89T65DggOmo1/img.png)
포트원 확인
결제 > 상세 내역 조회
에 들어가면 방금 결제한 내역을 확인할 수 있습니다.
먼저 필터에 테스트 결제를 추가합니다.
![](https://blog.kakaocdn.net/dn/cgmoJR/btsisp3I5Zp/gAbKXEeeNs2XkkVOR56VJk/img.png)
![](https://blog.kakaocdn.net/dn/bPBPTf/btsiuKMbngu/nDhym3ah3YjH4ovOoFGBQK/img.png)
결제 상세 내역을 확인하면 결제 고유 번호가 일치하는것을 확인할 수 있습니다.!! 👍
![](https://blog.kakaocdn.net/dn/dsujxi/btsis0bfygs/YR52HF3oWAafjjt1B0hPE0/img.png)
기타
더 자세한 연동 방법은 아래 링크를 참고하셔서 개발하시면 됩니다.^^
포트원 결제 연동 Docs
포트원 결제 연동 Docs
developers.portone.io
'0+ 스프링' 카테고리의 다른 글
Spring Boot에 Redis와 연동하여 처리율 제한 장치(Rate Limiter) 적용하기(Spring Boot + Redis + Bucket4j) (2) | 2023.10.22 |
---|