diff --git a/src/main/java/app/dearobjet/backend/domain/payment/PaymentBadRequestException.java b/src/main/java/app/dearobjet/backend/domain/payment/PaymentBadRequestException.java new file mode 100644 index 0000000..afc3164 --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/PaymentBadRequestException.java @@ -0,0 +1,11 @@ +// file: .../domain/payment/PaymentBadRequestException.java +package app.dearobjet.backend.domain.payment; + +import app.dearobjet.backend.global.exception.BusinessException; +import app.dearobjet.backend.global.exception.ErrorCode; + +public class PaymentBadRequestException extends BusinessException { + public PaymentBadRequestException(String msg) { + super(ErrorCode.INVALID_INPUT, msg); + } +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/PaymentController.java b/src/main/java/app/dearobjet/backend/domain/payment/PaymentController.java new file mode 100644 index 0000000..5a37a83 --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/PaymentController.java @@ -0,0 +1,44 @@ +package app.dearobjet.backend.domain.payment; + +import app.dearobjet.backend.domain.payment.dto.*; +import app.dearobjet.backend.global.api.ApiResponse; +import app.dearobjet.backend.global.auth.security.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1") +public class PaymentController { + + private final PaymentService paymentService; + + @PostMapping("/payments") + public ApiResponse create( + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody CreatePaymentRequest req + ) { + return ApiResponse.of(paymentService.createPayment(userDetails.getUserId(), req)); + } + + @PostMapping("/payments/{paymentId}/confirm") + public ApiResponse confirm( + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable Long paymentId, + @RequestBody ConfirmPaymentRequest req + ) { + paymentService.confirmPayment(userDetails.getUserId(), paymentId, req); + return ApiResponse.of(null); + } + + @PostMapping("/payments/{paymentId}/cancel") + public ApiResponse cancel( + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable Long paymentId, + @RequestBody CancelPaymentRequest req + ) { + paymentService.cancelPayment(userDetails.getUserId(), paymentId, req); + return ApiResponse.of(null); + } +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/PaymentNotFoundException.java b/src/main/java/app/dearobjet/backend/domain/payment/PaymentNotFoundException.java new file mode 100644 index 0000000..9d1aabe --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/PaymentNotFoundException.java @@ -0,0 +1,7 @@ +package app.dearobjet.backend.domain.payment; + +public class PaymentNotFoundException extends RuntimeException { + public PaymentNotFoundException(Long id) { + super("결제 정보를 찾을 수 없습니다. paymentId=" + id); + } +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/PaymentRepository.java b/src/main/java/app/dearobjet/backend/domain/payment/PaymentRepository.java new file mode 100644 index 0000000..3c494e4 --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/PaymentRepository.java @@ -0,0 +1,10 @@ +package app.dearobjet.backend.domain.payment; + +import app.dearobjet.backend.domain.payment.entity.Payment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface PaymentRepository extends JpaRepository { + Optional findByPaymentKey(String paymentKey); +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/PaymentService.java b/src/main/java/app/dearobjet/backend/domain/payment/PaymentService.java new file mode 100644 index 0000000..dd70f50 --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/PaymentService.java @@ -0,0 +1,135 @@ +package app.dearobjet.backend.domain.payment; + +import app.dearobjet.backend.domain.order.OrderAccessDeniedException; +import app.dearobjet.backend.domain.order.OrderNotFoundException; +import app.dearobjet.backend.domain.order.OrderRepository; +import app.dearobjet.backend.domain.order.entity.Order; +import app.dearobjet.backend.domain.order.entity.OrderStatus; +import app.dearobjet.backend.domain.payment.dto.*; +import app.dearobjet.backend.domain.payment.entity.*; +import app.dearobjet.backend.domain.payment.toss.TossPaymentsClient; +import app.dearobjet.backend.domain.payment.toss.TossPaymentsProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PaymentService { + + private final PaymentRepository paymentRepository; + private final OrderRepository orderRepository; + + private final TossPaymentsClient tossClient; + private final TossPaymentsProperties tossProps; + + @Transactional + public CreatePaymentResponse createPayment(Long userId, CreatePaymentRequest req) { + Order order = orderRepository.findById(req.getOrderId()) + .orElseThrow(() -> new OrderNotFoundException(req.getOrderId())); + + if (order.getUser() == null || !order.getUser().getId().equals(userId)) { + throw new OrderAccessDeniedException(order.getOrdersId()); + } + + if (order.getStatus() != OrderStatus.CREATED) { + throw new PaymentBadRequestException("결제 가능한 주문 상태가 아닙니다. status=" + order.getStatus()); + } + + if (!order.getTotalAmount().equals(req.getAmount())) { + throw new PaymentBadRequestException("결제 금액이 주문 금액과 다릅니다."); + } + + Payment payment = Payment.builder() + .order(order) + .provider(req.getProvider() == null ? PaymentProvider.TOSS : req.getProvider()) + .status(PaymentStatus.READY) + .amount(req.getAmount()) + .build(); + + Payment saved = paymentRepository.save(payment); + + return new CreatePaymentResponse( + saved.getPaymentId(), + order.getOrderNumber(), + saved.getAmount(), + tossProps.getClientKey() + ); + } + + @Transactional + public void confirmPayment(Long userId, Long paymentId, ConfirmPaymentRequest req) { + Payment payment = paymentRepository.findById(paymentId) + .orElseThrow(() -> new PaymentNotFoundException(paymentId)); + + Order order = payment.getOrder(); + if (order.getUser() == null || !order.getUser().getId().equals(userId)) { + throw new OrderAccessDeniedException(order.getOrdersId()); + } + + if (payment.getStatus() == PaymentStatus.DONE) return; + + if (!order.getOrderNumber().equals(req.getOrderId())) { + throw new PaymentBadRequestException("orderId가 주문번호와 다릅니다."); + } + + if (!payment.getAmount().equals(req.getPaidAmount())) { + throw new PaymentBadRequestException("승인 금액이 결제 금액과 다릅니다."); + } + + try { + tossClient.confirm(req.getPaymentKey(), req.getOrderId(), req.getPaidAmount()); + payment.markDone(req.getPaymentKey()); + order.updateStatus(OrderStatus.PAID); + } catch (Exception e) { + payment.markFailed("CONFIRM_FAILED"); + throw e; + } + } + + @Transactional + public void cancelPayment(Long userId, Long paymentId, CancelPaymentRequest req) { + Payment payment = paymentRepository.findById(paymentId) + .orElseThrow(() -> new PaymentNotFoundException(paymentId)); + + Order order = payment.getOrder(); + if (order.getUser() == null || !order.getUser().getId().equals(userId)) { + throw new OrderAccessDeniedException(order.getOrdersId()); + } + + if (payment.getStatus() != PaymentStatus.DONE) { + throw new PaymentBadRequestException("취소 가능한 결제 상태가 아닙니다. status=" + payment.getStatus()); + } + + Long cancelAmount = (req.getAmount() == null) ? payment.getAmount() : req.getAmount(); + + tossClient.cancel(payment.getPaymentKey(), cancelAmount, req.getReason()); + payment.markCanceled(); + order.cancel(req.getReason() == null ? "USER_CANCEL" : req.getReason()); + } + + @Transactional + public void handleWebhookToss(String paymentKey) { + if (paymentKey == null || paymentKey.isBlank()) return; + + Payment payment = paymentRepository.findByPaymentKey(paymentKey).orElse(null); + if (payment == null) return; + + if (payment.getStatus() == PaymentStatus.DONE) return; + + TossPaymentResponse tossPayment = tossClient.getPayment(paymentKey); + String status = tossPayment == null ? null : tossPayment.getStatus(); + String orderId = tossPayment == null ? null : tossPayment.getOrderId(); + Long totalAmount = tossPayment == null ? null : tossPayment.getTotalAmount(); + + if (status == null || orderId == null || totalAmount == null) return; + + if (!payment.getOrder().getOrderNumber().equals(orderId)) return; + if (!payment.getAmount().equals(totalAmount)) return; + + if ("DONE".equals(status)) { + payment.markDone(paymentKey); + payment.getOrder().updateStatus(OrderStatus.PAID); + } + } +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/PaymentWebhookController.java b/src/main/java/app/dearobjet/backend/domain/payment/PaymentWebhookController.java new file mode 100644 index 0000000..c4e14af --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/PaymentWebhookController.java @@ -0,0 +1,28 @@ +package app.dearobjet.backend.domain.payment; + +import app.dearobjet.backend.global.api.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1") +public class PaymentWebhookController { + + private final PaymentService paymentService; + + @PostMapping("/payments/webhooks/toss") + public ApiResponse tossWebhook(@RequestBody Map body) { + // paymentKey 뽑아서 결제 조회로 검증 후 반영 + Object paymentKey = body.get("paymentKey"); + if (paymentKey == null && body.get("data") instanceof Map data) { + Object pk = data.get("paymentKey"); + if (pk != null) paymentKey = pk; + } + + paymentService.handleWebhookToss(paymentKey == null ? null : paymentKey.toString()); + return ApiResponse.of(null); + } +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/dto/CancelPaymentRequest.java b/src/main/java/app/dearobjet/backend/domain/payment/dto/CancelPaymentRequest.java new file mode 100644 index 0000000..bd11571 --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/dto/CancelPaymentRequest.java @@ -0,0 +1,13 @@ +package app.dearobjet.backend.domain.payment.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class CancelPaymentRequest { + private Long amount; + private String reason; +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/dto/ConfirmPaymentRequest.java b/src/main/java/app/dearobjet/backend/domain/payment/dto/ConfirmPaymentRequest.java new file mode 100644 index 0000000..eda4f1c --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/dto/ConfirmPaymentRequest.java @@ -0,0 +1,14 @@ +package app.dearobjet.backend.domain.payment.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ConfirmPaymentRequest { + private String paymentKey; + private String orderId; + private Long paidAmount; +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/dto/CreatePaymentRequest.java b/src/main/java/app/dearobjet/backend/domain/payment/dto/CreatePaymentRequest.java new file mode 100644 index 0000000..772e6c0 --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/dto/CreatePaymentRequest.java @@ -0,0 +1,15 @@ +package app.dearobjet.backend.domain.payment.dto; + +import app.dearobjet.backend.domain.payment.entity.PaymentProvider; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class CreatePaymentRequest { + private Long orderId; + private PaymentProvider provider; + private Long amount; +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/dto/CreatePaymentResponse.java b/src/main/java/app/dearobjet/backend/domain/payment/dto/CreatePaymentResponse.java new file mode 100644 index 0000000..9572b07 --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/dto/CreatePaymentResponse.java @@ -0,0 +1,13 @@ +package app.dearobjet.backend.domain.payment.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class CreatePaymentResponse { + private final Long paymentId; + private final String orderNo; + private final Long amount; + private final String clientKey; +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/dto/TossCancelResponse.java b/src/main/java/app/dearobjet/backend/domain/payment/dto/TossCancelResponse.java new file mode 100644 index 0000000..881a7c3 --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/dto/TossCancelResponse.java @@ -0,0 +1,11 @@ +package app.dearobjet.backend.domain.payment.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class TossCancelResponse { + private String paymentKey; + private String status; +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/dto/TossConfirmResponse.java b/src/main/java/app/dearobjet/backend/domain/payment/dto/TossConfirmResponse.java new file mode 100644 index 0000000..9e27eb1 --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/dto/TossConfirmResponse.java @@ -0,0 +1,13 @@ +package app.dearobjet.backend.domain.payment.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class TossConfirmResponse { + private String paymentKey; + private String orderId; + private String status; + private Long totalAmount; +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/dto/TossPaymentResponse.java b/src/main/java/app/dearobjet/backend/domain/payment/dto/TossPaymentResponse.java new file mode 100644 index 0000000..049c74b --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/dto/TossPaymentResponse.java @@ -0,0 +1,13 @@ +package app.dearobjet.backend.domain.payment.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class TossPaymentResponse { + private String paymentKey; + private String orderId; + private String status; + private Long totalAmount; +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/entity/Payment.java b/src/main/java/app/dearobjet/backend/domain/payment/entity/Payment.java index 133f484..8873ee6 100644 --- a/src/main/java/app/dearobjet/backend/domain/payment/entity/Payment.java +++ b/src/main/java/app/dearobjet/backend/domain/payment/entity/Payment.java @@ -1,49 +1,61 @@ package app.dearobjet.backend.domain.payment.entity; -import app.dearobjet.backend.domain.classes.ClassReservation; import app.dearobjet.backend.domain.order.entity.Order; +import app.dearobjet.backend.global.common.entity.BaseTimeEntity; import jakarta.persistence.*; import lombok.*; +import java.time.OffsetDateTime; + @Entity @Table(name = "payments") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder -public class Payment { +public class Payment extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "payments_id") - private Long paymentsId; + private Long paymentId; - @Column(name = "payment_status") - private String paymentStatus; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "orders_id", nullable = false) + private Order order; - @Column(name = "payment_method") - private String paymentMethod; + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 10) + private PaymentProvider provider; - @Column(name = "payment_amount") - private Double paymentAmount; + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 10) + private PaymentStatus status; - @Column(name = "pg_id") - private String pgId; + @Column(nullable = false) + private Long amount; - @Column(name = "approved_at") - private java.time.LocalDateTime approvedAt; + @Column(length = 200) + private String paymentKey; // 토스 paymentKey - @Column(name = "created_at") - private java.time.LocalDateTime createdAt; + private OffsetDateTime approvedAt; + private OffsetDateTime canceledAt; - @Column(name = "last_modified_at") - private java.time.LocalDateTime lastModifiedAt; + @Column(length = 100) + private String failReason; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "reservation_id") - private ClassReservation reservation; + public void markDone(String paymentKey) { + this.status = PaymentStatus.DONE; + this.paymentKey = paymentKey; + this.approvedAt = OffsetDateTime.now(); + } - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "orders_id") - private Order order; -} + public void markCanceled() { + this.status = PaymentStatus.CANCELED; + this.canceledAt = OffsetDateTime.now(); + } + + public void markFailed(String reason) { + this.status = PaymentStatus.FAILED; + this.failReason = reason; + } +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/entity/PaymentProvider.java b/src/main/java/app/dearobjet/backend/domain/payment/entity/PaymentProvider.java new file mode 100644 index 0000000..a664dab --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/entity/PaymentProvider.java @@ -0,0 +1,5 @@ +package app.dearobjet.backend.domain.payment.entity; + +public enum PaymentProvider { + TOSS +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/entity/PaymentStatus.java b/src/main/java/app/dearobjet/backend/domain/payment/entity/PaymentStatus.java new file mode 100644 index 0000000..ea7486c --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/entity/PaymentStatus.java @@ -0,0 +1,8 @@ +package app.dearobjet.backend.domain.payment.entity; + +public enum PaymentStatus { + READY, + DONE, + CANCELED, + FAILED +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/toss/TossPaymentsClient.java b/src/main/java/app/dearobjet/backend/domain/payment/toss/TossPaymentsClient.java new file mode 100644 index 0000000..e54c30c --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/toss/TossPaymentsClient.java @@ -0,0 +1,63 @@ +package app.dearobjet.backend.domain.payment.toss; +import app.dearobjet.backend.domain.payment.dto.TossCancelResponse; +import app.dearobjet.backend.domain.payment.dto.TossConfirmResponse; +import app.dearobjet.backend.domain.payment.dto.TossPaymentResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class TossPaymentsClient { + + private final RestTemplate restTemplate; + private final TossPaymentsProperties props; + + public TossConfirmResponse confirm(String paymentKey, String orderId, Long amount) { + String url = props.getApiBase() + "/v1/payments/confirm"; + + Map body = Map.of( + "paymentKey", paymentKey, + "orderId", orderId, + "amount", amount + ); + + HttpEntity> entity = new HttpEntity<>(body, headers()); + return restTemplate.exchange(url, HttpMethod.POST, entity, TossConfirmResponse.class).getBody(); + } + + public TossPaymentResponse getPayment(String paymentKey) { + String url = props.getApiBase() + "/v1/payments/" + paymentKey; + HttpEntity entity = new HttpEntity<>(headers()); + return restTemplate.exchange(url, HttpMethod.GET, entity, TossPaymentResponse.class).getBody(); + } + + public TossCancelResponse cancel(String paymentKey, Long cancelAmount, String reason) { + String url = props.getApiBase() + "/v1/payments/" + paymentKey + "/cancel"; + + Map body = Map.of( + "cancelAmount", cancelAmount, + "cancelReason", (reason == null || reason.isBlank()) ? "USER_CANCEL" : reason + ); + + HttpEntity> entity = new HttpEntity<>(body, headers()); + return restTemplate.exchange(url, HttpMethod.POST, entity, TossCancelResponse.class).getBody(); + } + + private HttpHeaders headers() { + HttpHeaders h = new HttpHeaders(); + h.setContentType(MediaType.APPLICATION_JSON); + h.set("Authorization", "Basic " + basicAuth()); + return h; + } + + private String basicAuth() { + String raw = props.getSecretKey() + ":"; + return Base64.getEncoder().encodeToString(raw.getBytes(StandardCharsets.UTF_8)); + } +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/domain/payment/toss/TossPaymentsProperties.java b/src/main/java/app/dearobjet/backend/domain/payment/toss/TossPaymentsProperties.java new file mode 100644 index 0000000..e2690ca --- /dev/null +++ b/src/main/java/app/dearobjet/backend/domain/payment/toss/TossPaymentsProperties.java @@ -0,0 +1,15 @@ +package app.dearobjet.backend.domain.payment.toss; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Getter +@Setter +@ConfigurationProperties(prefix = "toss") +public class TossPaymentsProperties { + private String secretKey; + private String clientKey; + private String apiBase = "https://api.tosspayments.com"; +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/global/config/PropertiesConfig.java b/src/main/java/app/dearobjet/backend/global/config/PropertiesConfig.java new file mode 100644 index 0000000..0abf49c --- /dev/null +++ b/src/main/java/app/dearobjet/backend/global/config/PropertiesConfig.java @@ -0,0 +1,10 @@ +package app.dearobjet.backend.global.config; + +import app.dearobjet.backend.domain.payment.toss.TossPaymentsProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(TossPaymentsProperties.class) +public class PropertiesConfig { +} \ No newline at end of file diff --git a/src/main/java/app/dearobjet/backend/global/config/RestTemplateConfig.java b/src/main/java/app/dearobjet/backend/global/config/RestClientConfig.java similarity index 97% rename from src/main/java/app/dearobjet/backend/global/config/RestTemplateConfig.java rename to src/main/java/app/dearobjet/backend/global/config/RestClientConfig.java index be892c8..df29b3e 100644 --- a/src/main/java/app/dearobjet/backend/global/config/RestTemplateConfig.java +++ b/src/main/java/app/dearobjet/backend/global/config/RestClientConfig.java @@ -10,7 +10,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; @Configuration -public class RestTemplateConfig { +public class RestClientConfig { @Bean public RestTemplate restTemplate(ObjectMapper objectMapper) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ce167f3..815c694 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -80,3 +80,8 @@ cloud: credentials: access-key: ${AWS_ACCESS_KEY_ID} secret-key: ${AWS_SECRET_ACCESS_KEY} + +toss: + secretKey: ${TOSS_SECRET_KEY:} + clientKey: ${TOSS_CLIENT_KEY:} + apiBase: ${TOSS_API_BASE:https://api.tosspayments.com} \ No newline at end of file