티스토리 뷰

가상결제를 완료했으니 취소까지 이어서 해보겠다. 이 부분은 이전 카드 결제취소 부분과 매우 흡사다. 다만 계좌이체로 금액을 지불하였으므로 환불도 환불계좌로 돌려주는 방식으로 해줘야 한다는 점이다. 따라서 결제 취소 때 cancelReason 외에도 cancelAmount와 환불입금계좌 정보를 입력해서 요청해줘야 한다.

 

토스페이먼츠에서 명시하고 있는 환불 요청 양식을 봐보자.

 

토스페이먼츠 환불요청

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.tosspayments.com/v1/payments/{_페이먼트키_}/cancel"))
    .header("Authorization", "Basic _클라이언트_키")
    .header("Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("{\"cancelReason\":\"고객이 취소를 원함\",\"cancelAmount\":10000,\"refundReceiveAccount\":{\"bank\":\"우리\",\"accountNumber\":\"1000123456789\",\"holderName\":\"김토페\"}}"))
    .build();
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
  • 취소금액과 refundReceiveAccount를 통해서 bank, accountNumber, holderName 정보를 포함해서 보내고 있다.

토스페이먼츠 환불요청 응답

{
  "mId": "tvivarepublica",
  "version": "1.3",
  "transactionKey": "IrU6QBIv-nCQoujIoxiiY",
  "paymentKey": "IrU6QBIv-nCQoujIoxiiY",
  "orderId": "m-DKDQ8uXJIxc5CwP4e09",
  "orderName": "토스 티셔츠 외 2건",
  "method": "가상계좌",
  "status": "PARTIAL_CANCELED",
  "requestedAt": "2022-01-01T11:48:53+09:00",
  "approvedAt": "2022-01-01T11:49:35+09:00",
  "useEscrow": false,
  "cultureExpense": false,
  "card": null,
  "virtualAccount": {
    "accountNumber": "X6505831718354",
    "accountType": "일반",
    "bank": "우리",
    "customerName": "김토스",
    "dueDate": "2022-01-03T11:48:53+09:00",
    "expired": true,
    "settlementStatus": "INCOMPLETED",
    "refundStatus": "NONE"
  },
  "transfer": null,
  "mobilePhone": null,
  "giftCertificate": null,
  "cashReceipt": null,
  "discount": null,
  "cancels": [
    {
      "cancelReason": "고객 변심",
      "canceledAt": "2022-01-01T11:51:04+09:00",
      "cancelAmount": 10000,
      "taxFreeAmount": 0,
      "taxAmount": null,
      "refundableAmount": 0
    }
  ],
  "secret": null,
  "type": "NORMAL",
  "easyPay": null,
  "currency": "KRW",
  "totalAmount": 10000,
  "balanceAmount": 0,
  "suppliedAmount": 0,
  "vat": 0,
  "taxFreeAmount": 0
}
  • 취소요청에 대한 응답은 기존 취소 요청 응답 형식과 동일하다.

그럼 위 구조를 토대로 실제 코드로 구현해보도록 하자.

 

하지만 새롭게 구현하지는 않고 기존에 있던 카드결제 취소 로직에 덧붙이는 방식으로 수정할것이다.

결제취소 로직 가상계좌 결제취소 방식 추가 구현

CancelPayment Controller

@PostMapping
public CommonResult requestPaymentCancel(
        @ApiParam(value = "번호", required = true) @RequestParam Long memberSeq,
        @ApiParam(value = "예약 번호", required = true) @RequestParam Long reservationSeq,
        @ApiParam(value = "가상계좌 취소시 작성") @ModelAttribute CancelPaymentReq cancelPaymentReq
        ) {
    String resultMessage = cancelPaymentService.requestPaymentCancel(memberSeq, reservationSeq, cancelPaymentReq);
    if (resultMessage.equals("성공")) {
        return responseService.getSuccessResult();
    } else return responseService.getFailResult(
            -1,
            resultMessage
    );
}
  • 취소 파라미터를 받는 곳에 있어서 변경이 생겼다. CancelPayemtReq를 통해 취소를 위한 파라미터를 받도록 하였다.

CancelPayment 요청 DTO

@Data
public class CancelPaymentReq {
	@ApiModelProperty(value = "토스 측 주문 고유 번호")
	private String paymentKey;
	@ApiModelProperty(value = "결제 취소 사유")
	private String cancelReason;
	@ApiModelProperty(value = "환불 입금 기관")
	private String bank;
	@ApiModelProperty(value = "환불 입금 계좌번호")
	private String accountNumber;
	@ApiModelProperty(value = "환불 입금 예금주 성함")
	private String holderName;

	public RefundReceiveAccountDto getRefundAccountDto() {
		return RefundReceiveAccountDto.builder()
				.bank(bank)
				.accountNumber(accountNumber)
				.holderName(holderName)
				.build();
	}
}
  • 가상계좌 요청 시 해당 값들만 추가적으로 전달하기 위해 메소드를 추가해줬다.

CancelPayment Service

// 가상계좌 결제인 경우에만 추가 파라미터 넣어줌
if (payment.getPayType().equals(PAY_TYPE.VIRTUAL_ACCOUNT)) {
    param.put("cancelAmount", payment.getAmount());
    param.put("refundReceiveAccount", cancelPaymentReq.getRefundAccountDto());
}
  • 필요한 파라미터를 추가로 넣어줬다.

기존의 카드결제취소 로직과 크게 다르지않아서 생각보다 수정이 간단하다. 이제 예약 -> 결제 -> 결제취소 흐름을 다시 진행해보며 테스트를 해보자.

테스트

1. 가상계좌 결제 요청

2. 결제요청 응답값 확인

{
  "success": true,
  "code": 1,
  "message": "성공",
  "data": {
    "reservationSeq": null,
    "payType": "가상계좌",
    "amount": 3000,
    "orderId": "01b1afea-c5bf-4841-b537-434ca062fc96",
    "orderName": --상품명--
    "customerEmail": --이메일--
    "customerName": "최바보",
    "successUrl": --성공시 콜백 URL--
    "failUrl": --실패시 콜백 URL--
    "createDate": "2022-03-23 15:35:29",
    "paySuccessYn": "N",
    "validHours": 6,
    "cashReceiptType": "소득공제",
    "useEscrow": false
  }
}

 

 

3. 결제 요청으로 발급받은 orderId로 토스페이먼츠에 결제 요청

4. 결제 완료 후 성공 시 콜백 주소로 응답값

// 20220323153826
// http://localhost:9090/v1/api/payment/success?orderId=01b1afea-c5bf-4841-b537-434ca062fc96&paymentKey={페이먼트키}&amount=3000

{
  "success": true,
  "code": 1,
  "message": "성공",
  "data": {
    "version": "1.3",
    "paymentKey": --페이먼트키--
    "orderId": "01b1afea-c5bf-4841-b537-434ca062fc96",
    "orderName": --상품명--
    "currency": "KRW",
    "method": "가상계좌",
    "totalAmount": "3000",
    "balanceAmount": "3000",
    "suppliedAmount": "2727",
    "vat": "273",
    "status": "WAITING_FOR_DEPOSIT",
    "requestedAt": "2022-03-23T15:38:02+09:00",
    "approvedAt": null,
    "useEscrow": "false",
    "cultureExpense": "false",
    "card": null,
    "cancels": null,
    "type": "NORMAL",
    "virtualAccount": {
      "accountNumber": "X65659014475645",
      "accountType": "일반",
      "bank": "국민",
      "customerName": "김바보",
      "dueDate": "2022-03-23T21:38:02+09:00",
      "expired": "false",
      "settlementStatus": "INCOMPLETED",
      "refundStatus": "NONE"
    },
    "secret": --검증용_시크릿_값--
    "mid": null
  }
}

5. 가상계좌 입금 시 콜백 URL로 입금 완료 처리

하지만 현재는 로컬 테스트중이므로

토스페이먼츠에서 직접 입금처리로 변경했다.

6. 결제 취소 요청

토스페이먼츠 측에서 친절하게 잘못된 요청은 다 걸러서 적절한 오류메시지를 반환해주니 잘 써먹어보자.

환불계좌 예금주 성함 오류
계좌번호 오류
은행명 오류
취소 금액 오류

최종적으로 정상적인 요청을 보낸 경우

요청 내용과 동일하게 취소내역이 작성되어 응답이 온것을 확인할 수 있다.

 

스웨거에서 테스트를 진행해보자

작성한 코드대로 요청해보면 정상적으로 응답이 오는것을 볼 수 있다. 예금주명을 이상하게 주었고 토스페이먼츠에서 에러메시지를 발생시켰으며 해당 메시지를 취해서 반환값으로 사용하였다. 물론 정상적으로 요청하면 제대로 응답이 와서 처리되었다.

 

하지만 은행 기관명을 직접 입력하게 하는건 당연히 매우 불안정할 뿐더러 모든 은행을 지원하는게 아니라서 enum 클래스로 받는게 좋아보이므로 수정하였다. 

가능한 은행 기관 확인 : https://docs.tosspayments.com/guides/windows/virtual-account#가상계좌-발급이-지원되는-은행

 

은행 기관명 DTO

@Getter
@RequiredArgsConstructor
public enum REFUND_BANK_TYPE {
	경남은행("경남")    				// KYONGNAMBANK
	, 광주은행("광주")    				// GWANGJUBANK
	, KB국민은행("국민")    			// KOOKMIN
	, IBK기업은행("기업")    			// IBK
	, NH농협은행("농협")    			// NONGHYEOP
	, DGB대구은행("대구")    			// DAEGUBANK
	, 부산은행("부산")    				// BUSANBANK
	, 새마을금고("새마을")    			// SAEMAUL
	, Sh수협은행("수협")    			// SUHYEOP
	, 신한은행("신한")    				// SHINHAN
	, 우리은행("우리")    				// WOORI
	, 우체국예금보험("우체국")    		// POST
	, 전북은행("전북")    				// JEONBUKBANK
	, 케이뱅크("케이")    				// KBANK
	, 하나은행("하나");				// HANA

	private final String bankName;
}

이런식으로 은행명을 고를수 있게 나온다. 프론트에서도 어떤 값이 있는지 확인하기도 편하고 예외값이 올 가능성을 줄여주므로 좋은 방식이다.

 

이렇게 최종적으로 가상결제를 이용해 예약 후 결제요청으로 가상계좌번호를 발급받고 해당 계좌로 입금 처리 후(콜백) 결제취소까지 진행해보면서 가상결제 취소까지 완료하였다.

 

 


Reference

https://docs.tosspayments.com/guides/apis/cancel-payment

반응형
Comments
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday