이전 목표에 따라 ,
1. TOKEN을 매 요청마다 생성(POSTONE 측의 TOKEN이, 굉장히 자주 만료된다)
2. SERVICE와 CONTROLLER단, 그리고 내부적인 역할 분담을 진행하겠다.
@Service
@NoArgsConstructor
@Slf4j
public class AccessTokenService {
private final WebClient webClient = WebClient.builder().baseUrl("https://api.portone.io").build();
private final ObjectMapper objectMapper = new ObjectMapper();
public Mono<String> GetToken() {
PortoneTokenRequest portoneTokenRequest = new PortoneTokenRequest(내 SECRET )
Mono<String> PortoneTokenmono = webClient
.post()
.uri("/login/api-secret")
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(portoneTokenRequest))
.retrieve()
.bodyToMono(PortoneTokenResponse.class)
.map(PortoneTokenResponse::getAccessToken);
return PortoneTokenmono;
}
}
앞서 말한 1번과 2번의 유기성이 존재했는데,
*MONO로 통신함으로 인하여, TOKEN 생성을 위해 비 동기적인 호출을 한 순간 해당 MONO들이 CHAINING형식으로
연결되어야 하기 때문이다.
@Service
@NoArgsConstructor
public class ValidateService {
private final WebClient webClient = WebClient.builder().baseUrl("https://api.portone.io").build();
public Mono<PurChaseCheck> getpurchaseinfobyportone(String paymentid, String token)
{
Mono<PurChaseCheck> purchasecheck = webClient.get()
.uri("/payments/{paymentId}", paymentid)
.header("Authorization", "Bearer " + token)
.retrieve()
.bodyToMono(PurChaseCheck.class) ;
return purchasecheck ;
}
public class PurchaseService {
private final ProductRepsitory productRepsitory;
private final PaymentService paymentService;
public ResponseEntity<PurChaseCheck> validateandsave(PurChaseCheck purchasecheckbyportone,String paymentid)
{
if (purchasecheckbyportone.getAmount().getTotal() == 20000) {
// 문제 없으면 확인합니다. (검증 과정은 변경해야합니다)
int nowamount = productRepsitory.minusOneAmount("product01");
log.info("{}", paymentid);
// 감소 이후, 주문 정보를 저장합니다.
paymentService.SavePaymentInfo(
paymentid,
purchasecheckbyportone.getStatus(),
purchasecheckbyportone.getRequestedAt(),
purchasecheckbyportone.getOrderName(),
purchasecheckbyportone.getAmount().getTotal()
);
return ResponseEntity.ok(purchasecheckbyportone);
} else {
// 안될시 응답도 정해야됩니다.
log.info("problemoccur");
return ResponseEntity.badRequest().build();
}
}
결제 정보를 이용해서 PORTONE에서 결제 요청을 받아오는 부 , 그리고 결제 요청을 받아서 이를 검증하고(세부 검증 로직은 아직 정하지 않았다) 해당 응답을 다시 RETURN하는 부를 설계하였다.
@RestController
@Slf4j
@RequiredArgsConstructor
public class PurchaseController {
private final AccessTokenService accessTokenService ;
private final ValidateService validateService;
private final PurchaseService purchaseService;
@PostMapping("/payments/complete")
public Mono<ResponseEntity<PurChaseCheck>> validatepayment(@RequestBody ValidationRequest validation) {
return accessTokenService.GetToken()
.flatMap(token -> validateService.getpurchaseinfobyportone(validation.getPaymentId(), token)
.flatMap(purchasecheckresponsewebclient -> {
return Mono.just(purchaseService.validateandsave(purchasecheckresponsewebclient,validation.getPaymentId()));
}));
}
//crud 권한 문제는 해결 확인
}
해당 CONTROLLER 부를 설명하자면, ACCESS TOKEN을 받기 위한 MONO 시작 -> FLATMAP으로 해당 MONO 내부에서 다른 MONO 시작 (PORTONE의 정보를 받기 위해) , 그 내부에서 다시 MONO를 시작하여
검증이 완료되었을시 정보를 저장하는 형태로 구상하고, 이 MONO가 다시 RETURN 되었을때 모든 MONO가 종료된다.
만약 BLOCK을 써 비동기 내부에 동기적인 작업을 진행한다면?
여러가지 찾아본 결과, 해당 BLOCK도 가능은 하나 지양하라는 의견이 많았지만,
개인적으로 진행했을때는 BLOCK자체에 문제가 있어서 해당 METHOD를 사용할 수 조차 없었다.
왜 FLATMAP을 썼지?
FLATMAP을 사용한 이유는, 작업 1 -> 작업 2 -> 작업3이 모두 순차적으로 진행되며, (앞의 작업이 없으면 혼자 진행될 수 없다)
FLATMAP이 비동기적 작업들의 순차적 진행을 돕는 함수이기 때문이다.
또한, 한개의 MONO 객체는 비동기적으로 종료되지 않기 떄문에, 내부 값만을 뺴올 수 없다.
그러므로 해당 MONO 안에서 다음 작업을 진행하여야 한다. (이는 순차적 비동기를 고의적으로 WEBFLUX가 유발하였기 때문이다)
즉 이 모든 작업들을, 전체적으로 보았을땐 하나의 비동기 작업으로 처리할 수 있게 했다.
즉, 나는 이제 이 결제 과정을 수많은 사람들이 요청해도 병렬적으로 구상할 수 있게 되었다!
--다음 진행할 일 --
1. 결제 요청을 확인하고, 취소하는 METHOD를 구현한다. 2. 현재는 PRODUCT로 되어있는데, 이를 개수를 줄이지 말고 개인의 POINT를 올리는 형태로 변경하자. 3. 프론트와의 협업으로, 화면을 구상하자.
'프로젝트 > 장애인 PT 플랫폼, PTFD' 카테고리의 다른 글
결제 과정 완료 (1) | 2024.02.27 |
---|---|
프로젝트 TEST (0) | 2024.02.25 |
FRONT에서의 결제 + 결제 정보 저장 (0) | 2024.02.22 |
Portone을 활용한 수기 결제(백엔드 단에 전 권한 이임) (0) | 2024.02.21 |
PORTONE활용 결제부, 상품 관련 DB 추가. (0) | 2024.02.18 |