📅 2024. 04. 30 96일차
<script src="https://js.tosspayments.com/v1"></script>
<script th:inline="javascript">
const orderId = /*[[ ${order.id} ]]*/ null;
const buyerName = /*[[ ${order.buyer.name} ]]*/ null;
const orderName = /*[[ ${order.name} ]]*/ null;
const orderPayPrice = /*[[ ${order.calculatePayPrice} ]]*/ null;
</script>
<script>
const tossPayments = TossPayments("본인 API text 키");
function payment(){
const method = "카드"; // 카드 or 가상계좌
const paymentData ={
amount: orderPayPrice,
orderId: "order__" + orderId,
orderName,
customerName: buyerName,
successUrl: window.location.origin + "/success",
failUrl: window.location.origin + "/fail",
};
tossPayments.requestPayment(method,paymentData);
}
</script>
토스페이먼츠 결제 백엔드 연동
private final String SECRET_KEY = "test_sk_6bJXmgo28eBnx5GDX4Nj3LAnGKWx";
@PostConstruct
private void init() {
restTemplate.setErrorHandler(new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) {
return false;
}
@Override
public void handleError(ClientHttpResponse response) {
}
});
}
@RequestMapping("/{id}/success")
public String confirmPayment(
@RequestParam String paymentKey, @RequestParam String orderId, @RequestParam Long amount,
Model model) throws Exception {
HttpHeaders headers = new HttpHeaders();
// headers.setBasicAuth(SECRET_KEY, ""); // spring framework 5.2 이상 버전에서 지원
headers.set("Authorization", "Basic " + Base64.getEncoder().encodeToString((SECRET_KEY + ":").getBytes()));
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, String> payloadMap = new HashMap<>();
payloadMap.put("orderId", orderId);
payloadMap.put("amount", String.valueOf(amount));
HttpEntity<String> request = new HttpEntity<>(objectMapper.writeValueAsString(payloadMap), headers);
ResponseEntity<JsonNode> responseEntity = restTemplate.postForEntity(
"https://api.tosspayments.com/v1/payments/" + paymentKey, request, JsonNode.class);
if (responseEntity.getStatusCode() == HttpStatus.OK) {
JsonNode successNode = responseEntity.getBody();
model.addAttribute("orderId", successNode.get("orderId").asText());
String secret = successNode.get("secret").asText(); // 가상계좌의 경우 입금 callback 검증을 위해서 secret을 저장하기를 권장함
return "order/success";
} else {
JsonNode failNode = responseEntity.getBody();
model.addAttribute("message", failNode.get("message").asText());
model.addAttribute("code", failNode.get("code").asText());
return "order/fail";
}
}
@RequestMapping("/{id}/fail")
public String failPayment(@RequestParam String message, @RequestParam String code, Model model) {
model.addAttribute("message", message);
model.addAttribute("code", code);
return "order/fail";
}
백엔드에서 결제 무결성 체크
// OrderController
@RequestMapping("/{id}/success")
public String confirmPayment(
@PathVariable long id,
@RequestParam String paymentKey,
@RequestParam String orderId,
@RequestParam Long amount,
Model model
) throws Exception {
Order order = orderService.findForPrintById(id).get();
long orderIdInputed = Integer.parseInt(orderId.split("__")[1]);
if (id != orderIdInputed) {
throw new OrderIdNotMatchedException();
}
// ......=>
백엔드에서 무결성체크시 문제가 없다면 결제 완료 처리
@Transactional
public void payByTossPayments(Order order) {
Member buyer = order.getBuyer();
int payPrice = order.calculatePayPrice();
memberService.addCash(buyer, payPrice, "주문결제충전__토스페이먼츠결제");
memberService.addCash(buyer, payPrice * -1, "주문결제__토스페이먼츠결제");
order.setPaymentDone();
orderRepository.save(order);
}
예상 시나리오