Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
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
Tags more
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[WIL]20240825 결제 기능을 구현할 때 꼭 숙지해야 할 문제점 본문

개발Article

[WIL]20240825 결제 기능을 구현할 때 꼭 숙지해야 할 문제점

최밤빵 2024. 8. 25. 05:02

결제기능은 어려운 기능이라고 해서, 간단하게 구현하기로 했지만 알아둬야 할 문제점들이 많다. 기술매니저님께서 중요하다고 하는 문제점에 대해선 따로 더 세세하게 작성 할 예정이고, 오늘은 결제시스템 구현에 대해 알아보면서 알게 된 문제점들에 대해 간단하게 정리해보기로 했다. 

결제 기능을 구현할 때 꼭 숙지해야 할 문제점들은 다양하다. 결제는 사용자와의 신뢰를 구축하는 중요한 기능이기 때문에 보안, 데이터 일관성, 성능 등 여러 측면에서 신중하게 설계하고 구현해야 한다. 오늘 개발일지에서는 결제 기능을 구현할 때 반드시 고려해야 할 몇 가지 주요 문제점과 이를 해결하기 위한 간단한 예시와 코드 예시를 정리했다! 

 

1. 동시성 이슈

→ 문제점:
여러 사용자가 동시에 결제를 요청하면 데이터베이스의 동일한 상품에 접근할 때 문제가 발생할 수 있다. 예를 들어, 재고 관리 시스템에서 여러 사용자가 동시에 같은 상품을 결제하면, 재고 부족 오류가 발생하거나 중복 결제가 일어날 수 있다.

→ 해결 방안:
이를 해결하기 위해 낙관적 락(Optimistic Locking)비관적 락(Pessimistic Locking) 전략을 사용할 수 있다. 낙관적 락은 데이터를 업데이트할 때 버전 번호를 사용하여 충돌을 감지하고, 비관적 락은 데이터를 읽을 때부터 락을 걸어 다른 트랜잭션이 접근하지 못하도록 한다.

 

▽ 낙관적 락 (Optimistic Locking) 예시

import jakarta.persistence.*;

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int quantity;

    @Version // 낙관적 락을 위한 버전 필드
    private int version;

    // Getter 생략
}

→ 이 예시에서는 @Version 어노테이션을 사용하여 낙관적 락을 구현했다. 이 필드는 데이터베이스 테이블에 버전 정보를 추가하여 동시에 업데이트되는 경우 충돌을 감지할 수 있게 해준다.

 

▽ 비관적 락(Pessimistic Locking) 예시 

package com.example.payment.repository;

import com.example.payment.entity.Payment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.stereotype.Repository;
import jakarta.persistence.LockModeType;
import java.util.Optional;

@Repository
public interface PaymentRepository extends JpaRepository<Payment, Long> {

    // 비관적 락을 사용하여 결제 엔티티를 가져옴
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Optional<Payment> findById(Long id);
}

→ @Lock(LockModeType.PESSIMISTIC_WRITE) 어노테이션을 사용하여 findById() 메서드에서 해당 레코드를 읽을 때, 다른 트랜잭션이 해당 데이터를 수정할 수 없도록 한다. 이를 통해 결제 중 데이터 충돌을 방지할 수 있다.

 

▷ 동시성 이슈는 기술매니저님이 중요하다고 하셔서 따로 세세하게 다룰 예정! 

 

2. 멱등성(Idempotency)

→ 문제점:
결제 API는 멱등성을 가져야 한다. 예를 들어, 사용자가 결제 버튼을 여러 번 클릭했을 때, 동일한 결제가 여러 번 처리되지 않도록 해야 한다.

→ 해결 방안:
결제 요청마다 고유한 ID(예: requestId)를 발급하고, 해당 ID로 요청을 구분하여 처리한다. 서버는 이미 처리된 요청의 requestId를 기록하고, 동일한 requestId가 다시 들어올 경우 중복 결제를 방지한다.

 

▽ 코드 예시 (Idempotency Key 사용)

import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.Set;

@Service
public class PaymentService {

    private final Set<String> processedRequestIds = new HashSet<>();

    public String processPayment(String requestId, PaymentRequest paymentRequest) {
        if (processedRequestIds.contains(requestId)) {
            return "이미 처리된 요청입니다."; // 멱등성 보장
        }

        processedRequestIds.add(requestId);
        // 결제 처리 로직
        return "결제 완료";
    }
}

→ 이 코드에서는 processedRequestIds라는 Set을 사용하여 이미 처리된 요청의 requestId를 저장하고, 동일한 requestId가 다시 들어올 경우 결제를 처리하지 않는다.

 

▷ 멱등성은 PUT과PATCH로 공부하면 이해가 될거라고 하셔서 세세하게 따로 다룰 예정! 

 

3. 데이터 일관성 유지

→ 문제점:
결제 처리 과정에서 여러 단계의 데이터베이스 조작이 필요할 수 있다. 예를 들어, 결제 완료 후 주문 상태를 업데이트하고, 결제 내역을 저장하며, 사용자 포인트를 적립하는 등의 작업이 연속적으로 이루어진다. 이러한 작업 중 하나라도 실패하면 데이터가 일관되지 않을 수 있다.

→ 해결 방안:
이러한 문제를 해결하기 위해 트랜잭션(Transaction)을 사용한다. 트랜잭션은 여러 데이터베이스 작업을 하나의 작업으로 묶어, 모든 작업이 성공하거나 모두 롤백되도록 보장한다.

 

▽ 코드 예시 (트랜잭션 사용)

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Transactional // 트랜잭션 어노테이션
    public void completeOrder(PaymentRequest paymentRequest) {
        // 결제 처리
        processPayment(paymentRequest);

        // 주문 상태 업데이트
        updateOrderStatus(paymentRequest.getOrderId(), "COMPLETED");

        // 사용자 포인트 적립
        addUserPoints(paymentRequest.getUserId(), 100);
    }

    private void processPayment(PaymentRequest paymentRequest) {
        // 결제 처리 로직
    }

    private void updateOrderStatus(Long orderId, String status) {
        // 주문 상태 업데이트 로직
    }

    private void addUserPoints(Long userId, int points) {
        // 사용자 포인트 적립 로직
    }
}

  @Transactional 어노테이션을 사용하여 completeOrder 메서드를 트랜잭션으로 묶었다. 이로 인해 메서드 내의 모든 작업이 성공해야만 최종적으로 커밋되고, 하나라도 실패하면 롤백된다.

 

4. 보안 문제

문제점:
결제와 관련된 모든 요청과 데이터는 민감한 정보를 포함하고 있다. 따라서 이러한 데이터가 노출되거나 변조되는 경우 심각한 보안 문제가 발생할 수 있다.

해결 방안:
HTTPS 프로토콜을 사용하여 데이터 전송을 암호화하고, 결제 API 요청 시 JWT 또는 OAuth와 같은 인증 토큰을 사용하여 인증 및 인가를 관리한다.

 

▽ 코드 예시 (HTTPS 설정)

# application.properties 예시 (HTTPS 설정)
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=password
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=tomcat

이 예시에서는 Spring Boot 애플리케이션에서 HTTPS를 설정하는 방법을 보여준다. application.properties 파일에서 SSL 설정을 추가하여 서버가 HTTPS를 사용하도록 설정할 수 있다.

 

▶정리 

결제 기능을 구현할 때 초보 백엔드 개발자가 꼭 숙지해야 하는 문제점들을 정리했다. 동시성 이슈와 멱등성, 데이터 일관성 유지, 보안 문제를 주요 고려사항으로 다루고, 각 문제를 해결하기 위한 방법과 코드 예시를 만들어봤다. 결제 기능은 사용자 경험과 직접적인 관련이 있는 중요한 기능이라 이런 문제들을 신중히 고려하고 해결해야하는데.. 지금은 기본적인 흐름도 잘 잡히지않아서 문제점을 보안 할 수 있는 결제 기능을 구현할 수 있을지 모르겠다😟