티스토리 뷰





반응형

https://admin.portone.io/

위 사이트에서 가입 후

결제연동 > 연동정보에서 테스트 채널을 생성해준다.

 

대충 요렇게 필요한 채널키와 상점 아이디가 생성이 된다.

서버구성

결제 창 띄우기 위한 경로만 설정

더보기
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ViewController {
    @GetMapping("/")
    public String movePayment() {
        return "payment";
    }
}

 

 

스프링에 내장된 templates 활용했다.

(/src/main/resources/templates/payment.html)

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello, World!</title>
    <!-- jQuery -->
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
    <script src="https://cdn.portone.io/v2/browser-sdk.js"></script>
    <script>
        async function requestPay2() {
            const response = await PortOne.requestPayment({
                storeId: "your-storeId",
                channelKey: "your-channelKey",
                paymentId: `payment001`,
                orderName: "테스트 상품입니다.",
                totalAmount: 1000,
                currency: "KRW",
                payMethod: "CARD",
                customer: {
                    fullName: "포트원",
                    phoneNumber: "010-0000-1234",
                    email: "test@portone.io",
                },
            });

            if (response.code !== undefined) {
                // 오류 발생
                return alert(response.message);
            }

            console.log(response);
        }
    </script>
</head>
<body>

<h1>Hello, World!</h1>
<button onclick="requestPay2()">KG이니시스 결제</button>
</body>
</html>

 

이렇게해서 서버 띄우고 접근하면 결제창이 바로 호출된다.

Rest API로 결제정보 확인하기

api 호출을 위한 키발급을 받자!

그리고 스프링에서 application.properties에 저장하자!

api 호출할 때 쓸 base-url도 같이 셋팅!!

portone.v2.api.base-url=https://api.portone.io
portone.v2.api.secret=

 

우선 웹통신 해야하니까 webflux 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-webflux'

 

PortOneClient 추가

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.HashMap;
import java.util.Map;

@Component
public class PortOneClient {

    private final WebClient webClient;

    @Value("${portone.v2.api.secret}")
    private String apiSecret;

    public PortOneClient(@Value("${portone.v2.api.base-url}") String baseUrl, @Value("${portone.v2.api.secret}") String apiSecret) {
        this.webClient = WebClient.builder()
                .baseUrl(baseUrl)
                .defaultHeader("Authorization", "PortOne " + apiSecret)
                .defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                .build();
    }

    /**
     * 포트원 API 인증 토큰 발급
     */
    public String getAccessToken() {
        Map<String, String> requestBody = new HashMap<>();
        requestBody.put("apiSecret", apiSecret);

        Map<String, Object> response = webClient
                .post()
                .uri("/login/api-secret")
                .bodyValue(requestBody)
                .retrieve()
                .bodyToMono(Map.class)
                .block();

        if (response != null && response.containsKey("accessToken")) {
            String token = (String) response.get("accessToken");
            return token;
        }
        throw new RuntimeException("포트원 API 토큰 발급 실패");
    }

    /**
     * 결제 상세 조회
     */
    public Map<String, Object> getPaymentDetails(String paymentId) {
        return webClient
                .get()
                .uri("/payments/" + paymentId)
                .retrieve()
                .bodyToMono(Map.class)
                .block();
    }

    /**
     * 결제 취소
     */
    public Map<String, Object> cancelPayment(String paymentId, int cancelAmount, String reason) {
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("amount", cancelAmount);
        requestBody.put("reason", reason);

        return webClient
                .post()
                .uri("/payments/" + paymentId + "/cancel")
                .bodyValue(requestBody)
                .retrieve()
                .bodyToMono(Map.class)
                .block();
    }
}

 

PaymentApi 구성

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RequestMapping("/api/payment")
public class PaymentApi {

    private final PortOneClient portOneClient;
    private final ObjectMapper mapper;

    public PaymentApi(PortOneClient portOneClient) {
        this.portOneClient = portOneClient;
        this.mapper = new ObjectMapper();
    }

    @PostMapping("/complate")
    public String getPaymentDetails(@RequestBody Map<String, String> request) {
        String paymentId = request.get("paymentId");

        if (paymentId == null || paymentId.trim().isEmpty()) {
            return "paymentId 값이 필요합니다.";
        }

        // PortOne 클라이언트를 호출하여 결제 상세 정보를 조회합니다.
        Map<String, Object> paymentDetails = portOneClient.getPaymentDetails(paymentId);

        try {
            // JSON 형식으로 보기 좋게 변환하여 응답합니다.
            return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(paymentDetails);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return "JSON 변환 중 오류 발생";
        }
    }
}

 

결제창 호출 할 때 쓰인 paymentId를 넣고 호출해보면 정보를 잘 가져옵니다.

 

그 밖에 필요한 API 문서는 아래 링크를 참고!

https://developers.portone.io/api/rest-v2/overview?v=v2

반응형
댓글
반응형
최근에 달린 댓글
글 보관함
Total
Today
Yesterday
최근에 올라온 글
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31