Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

전남대 BE_나제법 5주차 과제(2단계) #303

Open
wants to merge 84 commits into
base: nove1080
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 73 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
5123667
init: 4주차 코드 업로드
nove1080 Jul 25, 2024
e9daf63
docs: README.md Step5 1단계 요구사항 반영
nove1080 Jul 26, 2024
7efd229
chore: build.gradle OpenFeign 의존성 추가
nove1080 Jul 26, 2024
0a0504e
feat: KakaoClient.java 추가
nove1080 Jul 26, 2024
1d4ff4d
feat: KakaoInfo.java 추가
nove1080 Jul 26, 2024
3e12882
feat: KakaoProfile.java 추가
nove1080 Jul 26, 2024
f9ebe69
feat: KakaoProperties.java 추가
nove1080 Jul 26, 2024
e52db54
feat: KakaoToken.java 추가
nove1080 Jul 26, 2024
6f3cf67
feat: KakaoAccount.java 추가
nove1080 Jul 26, 2024
e1ea3bb
feat: LoginService.java 추가
nove1080 Jul 26, 2024
12cfa98
feat: LoginResponse.java 필드명 변경
nove1080 Jul 26, 2024
2fb0b8d
feat: MemberService.java findOrCreateMember() 추가
nove1080 Jul 26, 2024
6f2c4b1
feat: Member.java Password 필드 삭제
nove1080 Jul 26, 2024
1102c74
feat: KakaoAccount.java toMember() 추가
nove1080 Jul 26, 2024
a83c654
feat: AuthenticationFilter.java ignorePaths 변경
nove1080 Jul 26, 2024
f2e2b93
feat: CreateMemberRequest.java Password 필드 삭제
nove1080 Jul 26, 2024
489f789
feat: WebConfig.java feignClient 추가
nove1080 Jul 26, 2024
8734b45
feat: data.sql Member 테이블 password 필드 삭제
nove1080 Jul 26, 2024
d712d3f
feat: JwtProvider.java Token 생성 시, SocialToken 클레임 추가
nove1080 Jul 26, 2024
db2e428
feat: JwtResolver.java resolveSocialToken 추가
nove1080 Jul 26, 2024
6d6c1ae
refactor: MemberService.java findOrCreateMember 회원 생성 시 dto 사용
nove1080 Jul 26, 2024
60a31a4
feat: ReadMemberResponse.java password 필드 삭제
nove1080 Jul 26, 2024
f5e2c2d
remove: 사용하지 않는 파일 제거
nove1080 Jul 26, 2024
2fbf175
feat: LoginController.java 추가
nove1080 Jul 26, 2024
4df68c1
feat: Application.java 어노테이션 추가
nove1080 Jul 26, 2024
0e270a1
test: MemberDummyDataProvider.java Password 필드 제거
nove1080 Jul 26, 2024
512fb5c
test: MemberServiceTest.java MemberService 변경에 따른 수정
nove1080 Jul 26, 2024
96c3bc5
test: MemberApiControllerTest.java 주석 처리
nove1080 Jul 26, 2024
0fd0b11
feat: MemberApiController.java 단일 회원 조회 API 추가
nove1080 Jul 26, 2024
2df743f
chore: .gitignore application-secret.yml 추가
nove1080 Jul 26, 2024
45ced48
removed cached
nove1080 Jul 26, 2024
ff75fd3
Merge branch 'nove1080' into step1
nove1080 Jul 26, 2024
f4a7c77
docs: README.md step5 2단계 요구사항 반영
nove1080 Jul 26, 2024
c2f5566
feat: Password.java 추가
nove1080 Jul 27, 2024
1a0e449
feat: Platform.java 추가
nove1080 Jul 27, 2024
b52b12a
feat: Member.java password, platform 필드 추가
nove1080 Jul 27, 2024
595ce3d
feat: MemberDetails.java platform 필드 추가
nove1080 Jul 27, 2024
bdad4aa
feat: LoginRequest.java 추가
nove1080 Jul 27, 2024
697f03d
feat: MemberService.java login() 추가
nove1080 Jul 27, 2024
11130e8
refactor: Product.java 사용하지 않는 메서드 삭제
nove1080 Jul 27, 2024
3b0b785
feat: CreateMemberRequest.java password 필드 추가
nove1080 Jul 27, 2024
7a68629
feat: ReadMemberResponse.java password 필드 추가
nove1080 Jul 27, 2024
e62bf41
chore: data.sql member의 password 필드 추가
nove1080 Jul 27, 2024
8acbdeb
feat: AuthenticationFilter.java ignorePaths 추가 등록
nove1080 Jul 27, 2024
9ef54f6
refactor: KakaoAccount.java Member 변환 로직 수정
nove1080 Jul 27, 2024
9ef88e9
test: 테스트 코드 수정
nove1080 Jul 27, 2024
e0b14df
feat: MemberApiController.java 회원가입/로그인 구현
nove1080 Jul 27, 2024
7835c2b
Merge remote-tracking branch 'origin/step1' into step1
nove1080 Jul 27, 2024
37cd143
feat: ProductViewController.java form 화면 추가
nove1080 Jul 28, 2024
9732f2b
feat: MemberDetails.java getEmailValue() 추가
nove1080 Jul 28, 2024
c8b954b
feat: script.js
nove1080 Jul 28, 2024
b61599c
feat: login-callback.html 추가
nove1080 Jul 28, 2024
74dca82
feat: login-call.html 추가
nove1080 Jul 28, 2024
9093d5a
feat: register-form.html 추가
nove1080 Jul 28, 2024
c5436da
refactor: index.html 주소변경
nove1080 Jul 28, 2024
5c338df
docs: README.md step5 2단계 요구사항 반영
nove1080 Jul 26, 2024
173b1b3
Merge remote-tracking branch 'origin/step2' into step2
nove1080 Jul 28, 2024
7ab1d22
Merge branch 'nove1080' into step2
nove1080 Jul 28, 2024
81385f0
feat: Token.java fromBearer() 추가
nove1080 Jul 28, 2024
1bbf57b
feat: KakaoClient.java sendMessage() 추가
nove1080 Jul 28, 2024
b786ed0
feat: KakaoCommerce.java 추가
nove1080 Jul 28, 2024
859b8bb
feat: KakaoMessageResult.java 추가
nove1080 Jul 28, 2024
099f45f
feat: CreateOrderRequest.java 추가
nove1080 Jul 28, 2024
15fb123
feat: KakaoProperties.java messageUrl 필드 추가
nove1080 Jul 28, 2024
9fe6cb3
feat: OrderResponse.java 추가
nove1080 Jul 28, 2024
0fd1a92
feat: ProductOptionService.java validateExistsProduct() 추가
nove1080 Jul 28, 2024
2fcfc26
feat: ReadProductResponse.java Options 필드 추가
nove1080 Jul 28, 2024
08c2fef
feat: OrderService.java 추가
nove1080 Jul 28, 2024
d32f7a1
feat: ProductApiController.java 상품 주문 API 구현
nove1080 Jul 28, 2024
a2a6af5
refactor: LoginService.java 리팩토링
nove1080 Jul 28, 2024
55a3efa
feat: script.js 사용하지 않는 함수 제거
nove1080 Jul 28, 2024
6425fc1
feat: ProductViewController.java loginForm() modelAttribute 추가
nove1080 Jul 28, 2024
e34cbd4
refactor: login-form.html 민감한 정보 제거
nove1080 Jul 28, 2024
abcf464
refactor: ReadProductResponse.java fromEntity 코드 리팩토링
nove1080 Jul 30, 2024
3da6446
feat: Order.java 추가
nove1080 Jul 30, 2024
7564f1a
feat: OrderRepository.java 추가
nove1080 Jul 30, 2024
3b5f4ff
feat: OrderResponse.java 불필요한 필드 제거 및 fromEntity 추가
nove1080 Jul 30, 2024
0ce5bb7
feat: OrderStatus.java 추가
nove1080 Jul 30, 2024
b304b28
feat: CreateOrderRequest.java toEntity() 추가
nove1080 Jul 30, 2024
eed7e4e
feat: OrderService.java 코드 리팩토링 및 주문 정보 저장 로직 추가
nove1080 Jul 30, 2024
9c32ba4
feat: ProductApiController.java 주문 생성 API MemberDetail 파라미터 추가
nove1080 Jul 30, 2024
0ecae3f
style: ReadProductResponse.java 줄바꿈 추가
nove1080 Jul 30, 2024
15ca808
feat: OrderRepository.java @Repository 추가
nove1080 Jul 30, 2024
0a7c565
feat: OrderService.java @Transactional(readOnly = true) 제거
nove1080 Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,44 @@
* 토큰 받기를 읽고 액세스 토큰을 추출한다.
* 앱 키, 인가 코드가 절대 유출되지 않도록 한다.
* 특히 시크릿 키는 GitHub나 클라이언트 코드 등 외부에서 볼 수 있는 곳에 추가하지 않는다.
* (선택) 인가 코드를 받는 방법이 불편한 경우 카카오 로그인 화면을 구현한다.
* (선택) 인가 코드를 받는 방법이 불편한 경우 카카오 로그인 화면을 구현한다.

### 🚀 2단계 - 주문하기
***
#### 기능 요구 사항
카카오톡 메시지 API를 사용하여 주문하기 기능을 구현한다.

* 주문할 때 수령인에게 보낼 메시지를 작성할 수 있다.
* 상품 옵션과 해당 수량을 선택하여 주문하면 해당 상품 옵션의 수량이 차감된다.
* 해당 상품이 위시 리스트에 있는 경우 위시 리스트에서 삭제한다.
* 나에게 보내기를 읽고 주문 내역을 카카오톡 메시지로 전송한다.
* 메시지는 메시지 템플릿의 기본 템플릿이나 사용자 정의 템플릿을 사용하여 자유롭게 작성한다.

아래 예시와 같이 HTTP 메시지를 주고받도록 구현한다.

##### Request
```
POST /api/orders HTTP/1.1
Authorization: Bearer {token}
Content-Type: application/json

{
"optionId": 1,
"quantity": 2,
"message": "Please handle this order with care."
}
```

##### Response
```
HTTP/1.1 201 Created
Content-Type: application/json

{
"id": 1,
"optionId": 1,
"quantity": 2,
"orderDateTime": "2024-07-21T10:00:00",
"message": "Please handle this order with care."
}
```
4 changes: 4 additions & 0 deletions src/main/java/gift/authentication/token/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public static Token from(String value) {
return new Token(value);
}

public static Token fromBearer(String value) {
return new Token(value.replace("Bearer ", ""));
}

public String getValue() {
return value;
}
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/gift/config/KakaoProperties.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gift.config;

import java.net.URI;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;

Expand All @@ -13,17 +14,20 @@ public class KakaoProperties {
private final String userInfoUrl;
private final String tokenUrl;
private final String responseType;
private final String messageUrl;

@ConstructorBinding
public KakaoProperties(String clientId, String redirectUri, String contentType,
String grantType, String userInfoUrl, String tokenUrl, String responseType) {
String grantType, String userInfoUrl, String tokenUrl, String responseType,
String messageUrl) {
this.clientId = clientId;
this.redirectUri = redirectUri;
this.contentType = contentType;
this.grantType = grantType;
this.userInfoUrl = userInfoUrl;
this.tokenUrl = tokenUrl;
this.responseType = responseType;
this.messageUrl = messageUrl;
}

public String getClientId() {
Expand Down Expand Up @@ -53,4 +57,20 @@ public String getTokenUrl() {
public String getResponseType() {
return responseType;
}

public String getMessageUrl() {
return messageUrl;
}

public URI getUserInfoUrlAsUri() {
return URI.create(userInfoUrl);
}

public URI getTokenUrlAsUri() {
return URI.create(tokenUrl);
}

public URI getMessageUrlAsUri() {
return URI.create(messageUrl);
}
}
35 changes: 12 additions & 23 deletions src/main/java/gift/service/LoginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
import gift.web.client.dto.KakaoAccount;
import gift.web.client.dto.KakaoInfo;
import gift.web.dto.response.LoginResponse;
import gift.web.validation.exception.client.InvalidCredentialsException;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -47,30 +44,22 @@ public LoginResponse kakaoLogin(final String authorizationCode){
return new LoginResponse(accessToken.getValue());
}

private KakaoToken getToken(String authorizationCode) {
try {
return kakaoClient.getToken(
new URI(kakaoProperties.getTokenUrl()),
authorizationCode,
kakaoProperties.getClientId(),
kakaoProperties.getRedirectUri(),
kakaoProperties.getGrantType());
} catch (URISyntaxException e) {
throw new InvalidCredentialsException(e);
}
private KakaoToken getToken(final String authorizationCode) {
return kakaoClient.getToken(
kakaoProperties.getTokenUrlAsUri(),
authorizationCode,
kakaoProperties.getClientId(),
kakaoProperties.getRedirectUri(),
kakaoProperties.getGrantType());
}

private KakaoInfo getInfo(KakaoToken kakaoToken) {
try {
return kakaoClient.getKakaoInfo(
new URI(kakaoProperties.getUserInfoUrl()),
getBearerToken(kakaoToken));
} catch (URISyntaxException e) {
throw new InvalidCredentialsException(e);
}
private KakaoInfo getInfo(final KakaoToken kakaoToken) {
return kakaoClient.getKakaoInfo(
kakaoProperties.getUserInfoUrlAsUri(),
getBearerToken(kakaoToken));
}

private String getBearerToken(KakaoToken kakaoToken) {
private String getBearerToken(final KakaoToken kakaoToken) {
return kakaoToken.getTokenType() + " " + kakaoToken.getAccessToken();
}
}
71 changes: 71 additions & 0 deletions src/main/java/gift/service/OrderService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package gift.service;

import gift.authentication.token.JwtResolver;
import gift.authentication.token.Token;
import gift.config.KakaoProperties;
import gift.web.client.KakaoClient;
import gift.web.client.dto.KakaoCommerce;
import gift.web.dto.request.order.CreateOrderRequest;
import gift.web.dto.response.order.OrderResponse;
import gift.web.dto.response.product.ReadProductResponse;
import gift.web.dto.response.productoption.SubtractProductOptionQuantityResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
public class OrderService {

private final KakaoClient kakaoClient;
private final JwtResolver jwtResolver;
private final ProductOptionService productOptionService;
private final ProductService productService;
private final KakaoProperties kakaoProperties;

public OrderService(KakaoClient kakaoClient, JwtResolver jwtResolver, ProductOptionService productOptionService,
ProductService productService, KakaoProperties kakaoProperties) {
this.kakaoClient = kakaoClient;
this.jwtResolver = jwtResolver;
this.productOptionService = productOptionService;
this.productService = productService;
this.kakaoProperties = kakaoProperties;
}

@Transactional
public OrderResponse createOrder(String accessToken, Long productId, CreateOrderRequest request) {
//상품 옵션 수량 차감
SubtractProductOptionQuantityResponse subtractOptionStockResponse = productOptionService.subtractOptionStock(request);

ReadProductResponse product = productService.readProductById(productId);
KakaoCommerce kakaoCommerce = KakaoCommerce.of(product, request.getMessage());

sendOrderMessageIfSocialMember(accessToken, kakaoCommerce);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

카카오톡 메시지 전송하는건 같은 트랜잭션에 묶일 필요가 있을까요?
주문 및 수량 차감은 핵심 로직인데, 카카오톡 메시지 전송 api 가 실패해서 주문까지 실패하면 안될 . 것같아요
외부 api를 사용할 때는 이런 트랜잭션에 대해서 잘 고려해야됩니다!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 그리고 주문 데이터는 따로 저장안할까요?

Copy link
Author

@nove1080 nove1080 Jul 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 그리고 주문 데이터는 따로 저장안할까요?

과제 진행이 좀 늦어져서 제출기한을 고려할 때 빠듯할 것 같아서 주문 데이터를 저장하는 부분은 만들지 않았습니다! 이제 만들도록 하겠습니다

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

카카오톡 메시지 전송하는건 같은 트랜잭션에 묶일 필요가 있을까요? 주문 및 수량 차감은 핵심 로직인데, 카카오톡 메시지 전송 api 가 실패해서 주문까지 실패하면 안될 . 것같아요 외부 api를 사용할 때는 이런 트랜잭션에 대해서 잘 고려해야됩니다!

이 부분은 생각하지 못했네요 아무 생각없이 트랜잭션으로 묶었던 것 같습니다

Copy link
Author

@nove1080 nove1080 Jul 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 동건님! 리팩토링을 하는 과정에서 궁금한 부분이 있어서 여쭤보고 싶습니다!

@Transactional
public OrderResponse createOrder(String accessToken, Long productId, Long memberId, CreateOrderRequest request) {
    //상품 옵션 수량 차감
    productOptionService.subtractOptionStock(request);

    //주문 정보 저장
    Order order = orderRepository.save(request.toEntity(memberId, productId));

    //카카오 메시지 전송
    sendOrderMessageIfSocialMember(accessToken, productId, request);
    return OrderResponse.from(order);
}

주문 도메인이 추가되면서 주문 생성 시, 해당 주문을 DB에 저장하도록 구현하였습니다.
이 때, 주문 저장 로직이 추가되면서 해당 메서드를 트랜잭션으로 묶어야겠다고 생각했습니다.

주문 정보 저장이 실패하였지만, 상품 옵션 수량이 차감되는 문제가 발생할 수 있어 같은 트랜잭션에서 수행되도록 하는 것이 좋겠다고 생각했습니다.

그런데 이 경우에는 카카오 메시지 전송에 실패하여 주문이 생성되지 않는 경우도 존재하는데

Q1. 이런 경우에는 이 부분만 트랜잭션에서 제외시켜줄 수 있나요? 그러지 못한다면 일종의 트레이드 오프라고 생각해야할까요?

Q2. 만약 트랜잭션을 붙이지 않은 경우

코드의 실행 순서 상 상품 옵션 수량 차감이 먼저 수행되기 때문에 주문 정보를 저장에 성공한 경우에는 100% 상품 옵션 수량이 성공적으로 차감되었다고 보장할 수 있나요?

한 트랜잭션 안에서 실행되지 않은 작업들은 실행 순서를 신중하게 고려해야하나요?

return new OrderResponse(
productId,
request.getOptionId(),
subtractOptionStockResponse.getStock(),
request.getQuantity(),
product.getName(),
request.getMessage());
}

/**
* 소셜 로그인을 통해 주문한 경우 카카오톡 메시지를 전송합니다
* @param accessToken Bearer Token
* @param kakaoCommerce 카카오 상거래 메시지
*/
private void sendOrderMessageIfSocialMember(String accessToken, KakaoCommerce kakaoCommerce) {
jwtResolver.resolveSocialToken(Token.fromBearer(accessToken))
.ifPresent(socialToken -> {
String json = kakaoCommerce.toJson();
kakaoClient.sendMessage(
kakaoProperties.getMessageUrlAsUri(),
getBearerToken(socialToken),
json);
});
}

private String getBearerToken(String token) {
return "Bearer " + token;
}
}
21 changes: 20 additions & 1 deletion src/main/java/gift/service/ProductOptionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import gift.domain.ProductOption;
import gift.repository.ProductOptionRepository;
import gift.repository.ProductRepository;
import gift.web.dto.request.order.CreateOrderRequest;
import gift.web.dto.request.productoption.CreateProductOptionRequest;
import gift.web.dto.request.productoption.SubtractProductOptionQuantityRequest;
import gift.web.dto.request.productoption.UpdateProductOptionRequest;
Expand All @@ -21,13 +23,17 @@
public class ProductOptionService {

private final ProductOptionRepository productOptionRepository;
private final ProductRepository productRepository;

public ProductOptionService(ProductOptionRepository productOptionRepository) {
public ProductOptionService(ProductOptionRepository productOptionRepository,
ProductRepository productRepository) {
this.productOptionRepository = productOptionRepository;
this.productRepository = productRepository;
}

@Transactional
public CreateProductOptionResponse createOption(Long productId, CreateProductOptionRequest request) {
validateExistsProduct(productId);
String optionName = request.getName();
validateOptionNameExists(productId, optionName);

Expand All @@ -36,6 +42,15 @@ public CreateProductOptionResponse createOption(Long productId, CreateProductOpt
return CreateProductOptionResponse.fromEntity(createdOption);
}

/**
* 상품이 존재하는지 확인합니다.
* @param productId 상품 아이디
*/
private void validateExistsProduct(Long productId) {
productRepository.findById(productId)
.orElseThrow(() -> new ResourceNotFoundException("상품 아이디: ", productId.toString()));
}

/**
* 상품 옵션 이름이 이미 존재하는지 확인합니다.<br>
* 이미 존재한다면 {@link AlreadyExistsException} 을 발생시킵니다.
Expand Down Expand Up @@ -131,6 +146,10 @@ public SubtractProductOptionQuantityResponse subtractOptionStock(Long optionId,
return SubtractProductOptionQuantityResponse.fromEntity(option);
}

public SubtractProductOptionQuantityResponse subtractOptionStock(CreateOrderRequest request) {
return subtractOptionStock(request.getOptionId(), new SubtractProductOptionQuantityRequest(request.getQuantity()));
}

@Transactional
public void deleteOption(Long optionId) {
ProductOption option = productOptionRepository.findById(optionId)
Expand Down
20 changes: 19 additions & 1 deletion src/main/java/gift/web/client/KakaoClient.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package gift.web.client;

import static org.springframework.http.HttpHeaders.AUTHORIZATION;

import gift.authentication.token.KakaoToken;
import gift.web.client.dto.KakaoInfo;
import gift.web.client.dto.KakaoMessageResult;
import java.net.URI;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;

Expand All @@ -14,7 +19,7 @@ public interface KakaoClient {
@PostMapping
KakaoInfo getKakaoInfo(
URI uri,
@RequestHeader("Authorization") String accessToken);
@RequestHeader(AUTHORIZATION) String accessToken);

@PostMapping
KakaoToken getToken(
Expand All @@ -23,4 +28,17 @@ KakaoToken getToken(
@RequestParam("client_id") String clientId,
@RequestParam("redirect_uri") String redirectUrl,
@RequestParam("grant_type") String grantType);

/**
* 카카오톡 메시지 - 나에게 보내기
* @param uri {@link https://kapi.kakao.com/v2/api/talk/memo/default/send} 으로 고정
* @param accessToken Bearer Token
* @param templateObject 메시지 구성 요소를 담은 객체(Object) 피드, 리스트, 위치, 커머스, 텍스트, 캘린더 중 하나
*/
@PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
KakaoMessageResult sendMessage(
URI uri,
@RequestHeader(AUTHORIZATION) String accessToken,
@RequestBody String templateObject
);
}
Loading