Java

[MSA] e-commerce API; 구현2.3 - payment

ride-dev 2024. 6. 19. 15:03

[MSA] e-commerce API; 프로젝트 흐름

[MSA] e-commerce API; 구현1 - 설정(config-server, eureka-server, gateway)

[MSA] e-commerce API; 구현2 - customer, product, payment, order, notification

(앞선 게시글에 작성한 내용은 생략할 예정입니다)

 

작성한 ERD를 참조하여 구현합니다.

Payment

order에서 결제(Payment)요청을 합니다.

Product와 마찬가지로 Payment 또한 일관성이 중요한 데이터이므로 RDBMS에 저장합니다.

(RDBMS는 postgreSQL을 사용했습니다)

 

아래는 payment 프로젝트의 의존성입니다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.kafka:spring-kafka'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'io.micrometer:micrometer-tracing-bridge-brave'
    implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'org.postgresql:postgresql'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.kafka:spring-kafka-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

kafka와 zipkin에 대한 것은 나중에 다루도록 하겠습니다.

 

payment의 설정파일인 application.yml은 아래와 같습니다.

spring:
  application:
    name: payment-service
  config:
    import: optional:configserver:http://localhost:8888

이제 config-server에 product-service.yml을 작성합니다.

server:
  port: 8060
spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/payment
    username: ride
    password: password
  jpa:
    hibernate:
      ddl-auto: create
    database: postgresql
    database-platform: org.hibernate.dialect.PostgreSQLDialect

  kafka:
    producer:
      bootstrap-servers: localhost:9092
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
      properties:
        spring.json.type.mapping: paymentConfirmation:com.ride.ecommerce.notification.PaymentNotificationRequest

개발 편의상 jpa.hibernate.ddl-auto를 create로 설정하였습니다.

아래는 payment도메인이 사용하는 postgreSQL의 paymentdatabase입니다.

 

JpaAuditing을 사용하기 위해 main class에 아래 처럼 @EnableJpaAuditing 주석을 추가합니다.

package com.ride.ecommerce;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class PaymentApplication {

    public static void main(String[] args) {
       SpringApplication.run(PaymentApplication.class, args);
    }

}

1.0 디렉토리 구조

payment의 디렉토리 구조는 아래와 같습니다.

payment디렉토리에 payment 도메인과 연관된 Customer, PaymentMethod 등에 대한 코드, 파일을 구성합니다.

1.1 payment

1.1.1 Payment class, PaymentMethod enum, Customer record

package com.ride.ecommerce.payment;

import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.math.BigDecimal;
import java.time.LocalDateTime;

import static jakarta.persistence.EnumType.STRING;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "payment")
public class Payment {
    @Id
    @GeneratedValue
    private Integer id;
    private BigDecimal amount;
    @Enumerated(STRING)
    private PaymentMethod paymentMethod;
    private Integer orderId;
    @CreatedDate
    @Column(updatable = false, nullable = false)
    private LocalDateTime createdDate;
    @LastModifiedDate
    @Column(insertable = false)
    private LocalDateTime lastModifiedDate;
}

@Table 을 통해 테이블명을 지정합니다. 필요에 따라 index 설정을 추가합니다.

Payment Entity의 PaymentMethod를 값타입으로 설정합니다.

JpaAuditing 설정을 통해, 객체의 생성 시간, 업데이트 시간을 저장합니다.

package com.ride.ecommerce.payment;

public enum PaymentMethod {
    PAYPAL,
    CREDIT_CARD,
    VISA,
    MASTER_CARD,
    BITCOIN
}
package com.ride.ecommerce.payment;


import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import org.springframework.validation.annotation.Validated;

@Validated
public record Customer(
        String id,
        @NotNull(message = "Firstname is required")
        String firstname,
        @NotNull(message = "Lastname is required")
        String lastname,
        @NotNull(message = "Email is required")
        @Email(message = "The customer is not correctly formatted")
        String email
) {
}

1.1.2 PaymentController class

package com.ride.ecommerce.payment;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * REST controller for managing payments.
 */
@RestController
@RequestMapping("/api/v1/payments")
@RequiredArgsConstructor
public class PaymentController {
    
    private final PaymentService service;

    /**
     * POST /api/v1/payments : Create a new payment.
     *
     * @param request the payment request containing payment details
     * @return the ResponseEntity with status 200 (OK) and the ID of the created payment
     */
    @PostMapping
    public ResponseEntity<Integer> createPayment(
            @RequestBody @Valid PaymentRequest request
    ) {
        return ResponseEntity.ok(service.createPayment(request));
    }
}

1.1.3 PaymentRequest record

package com.ride.ecommerce.payment;

import java.math.BigDecimal;

public record PaymentRequest(
        Integer id,
        BigDecimal amount,
        PaymentMethod paymentMethod,
        Integer orderId,
        String orderReference,
        Customer customer
) {
}

1.1.4 PaymentService, PaymentMapper class

package com.ride.ecommerce.payment;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * Service class for managing payments.
 */
@Service
@RequiredArgsConstructor
public class PaymentService {

    private final PaymentRepository repository;
    private final PaymentMapper mapper;

    /**
     * Creates a new payment and sends a notification.
     *
     * @param request the payment request containing payment details
     * @return the ID of the created payment
     */
    public Integer createPayment(PaymentRequest request) {
        var payment = repository.save(mapper.toPayment(request));

		// notification
        
        return payment.getId();
    }
}
package com.ride.ecommerce.payment;

import org.springframework.stereotype.Service;

/**
 * Mapper class for converting between PaymentRequest DTO and Payment entity.
 */
@Service
public class PaymentMapper {

    /**
     * Converts a PaymentRequest DTO to a Payment entity.
     *
     * @param request the payment request containing payment details
     * @return the Payment entity
     */
    public Payment toPayment(PaymentRequest request) {
        return Payment.builder()
                .id(request.id())
                .orderId(request.orderId())
                .paymentMethod(request.paymentMethod())
                .amount(request.amount())
                .build();
    }
}

1.1.5 PaymentRepository interface

package com.ride.ecommerce.payment;

import org.springframework.data.jpa.repository.JpaRepository;

public interface PaymentRepository extends JpaRepository<Payment, Integer> {
}

1.2 config - kafka

kafka 게시글에서 다루겠습니다.

1.3 notification

notification 게시글에서 다루겠습니다.

728x90