후속 검증까지 완료했다면 이제 결제를 취소하는 기능을 추가해보려고한다.
후속 검증에서 두개의 값이 달랐을때, 또는 사용자가 결제 취소를 원할때에 진행해보려고한다.
포트원 API에서 설명한
(
[1] /users/getToken 에서 API 키 & API secret을 사용해 access_token을 발급받는다.
[2] API호출 시 access_token을 같이 전달한다.
Authorization: access_token 또는 X-ImpTokenHeader: access_token으로 HTTP header를 통해 전달합니다.(?_token=access_token과 같이 url query로 전달하는 방식은 URL노출 시 심각한 보안문제가 있을 수 있어 deprecated되었습니다. 다음 버전에서 제거될 예정)
3access_token을 전달하는 header는 Authorization을 우선적으로 적용하는 것을 원칙으로 합니다. by ISSUE in GitHub
)
포트원 API 방법을 사용해서 HttpHeader를 만들어줘서 헤더에 토큰값을 넣어주고 body에 결제 정보 데이터를 넣어줘서 요청하는 방법도 있지만,
이전에 후속 검증을 진행할때에 사용했던 방식인 Iamport의 라이브러리를 참고해서 paymentByImpUid() 함수를 사용했다면 이번에는 IamportClient의 cancelpaymentByImpUid() 함수를 사용해서 간단하게 취소 처리를 해보려고한다.
if (rsp.success) {
//서버 검증 요청 부분
let data = {
imp_uid: rsp.imp_uid, // 결제 고유번호
merchant_uid: rsp.merchant_uid, // 주문번호
amount: rsp.paid_amount
}
$.ajax({
url: '/payment/verification',
method: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data)
}).done(function (data) {
// console.log(data);//주문 금액이 들어있음 // 위의 rsp.paid_amount 와 data.response.amount를 비교한후 로직 실행 (import 서버검증)
if (rsp.paid_amount == data.response.amount) {
console.log(data);
alert("결제 및 결제검증완료");
cancelPay(rsp);// 테스트를 위한 위치!!!!!!!!!!!!!
} else {
alert("결제 검증 실패 및 결제 취소");
}
});
}
현재 코드를 확인해보면 결제가 끝난후에 rsp의 상태가 success라면 후속 검증을 위해서 ajax통신을 실행한다. 이때 후속검증을 실행했을때에 실제 결제 금액과 결제 요청할때의 금액 정보가 같다면 결제가 정상적으로 진행된것이고 다르다면 문제가 생긴것이다.
우리는 금액 정보가 같지않아서 문제가 생겼을때에 취소처리를 하기위한 기능을 설계하고있는 것이다.
하지만!!
취소가 정상적으로 진행되는지를 알아보기위해서는 결제 했을때에 바로 취소를 진행하는 방식으로 설계해보았다.
따라서, 주문금액이 같아서 문제가 발생하지 않는경우에 그냥 결제를 바로 취소해 버리도록 cancelPay()함수를 위치시켰다.
function cancelPay(rsp) {
$.ajax({
url: '/payment/cancel',
method: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({
"imp_uid": rsp.imp_uid,
"reason": "결제 검증실패",
"checksum": rsp.paid_amount
})
}).done(function () {
alert("결제 검증 실패로 결제가 취소되었습니다!");
}).fail(function (error) {
alert(JSON.stringify(error));
});
}
지정한 url로 ajax요청을 하고 데이터는 imp_uid,와 결제 취소 이유, checksum을 넣어주었다.
package com.qkrtprjs.happyexercise.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class PaymentCancelDto {
private String imp_uid;
private String reason;
private int checksum;
@Builder
public PaymentCancelDto(String imp_uid, String reason, int checksum) {
this.imp_uid = imp_uid;
this.reason = reason;
this.checksum = checksum;
}
}
@PostMapping("/payment/cancel")
private IamportResponse<Payment> cancelPaymentByImpUid(@RequestBody PaymentCancelDto paymentCancelDto) throws IamportResponseException, IOException {
String impUid = paymentCancelDto.getImp_uid();
return iamportClient.cancelPaymentByImpUid(new CancelData(impUid, true));
}
dto로 넘겨받은 데이터에서 impUid를 빼주고 이 impUid를 이용해서 CancelData를 생성해줍니다.
package com.siot.IamportRestClient.request;
import java.math.BigDecimal;
import com.google.gson.annotations.SerializedName;
public class CancelData {
@SerializedName("imp_uid")
private String imp_uid;
@SerializedName("merchant_uid")
private String merchant_uid;
@SerializedName("amount")
private BigDecimal amount;
@SerializedName("tax_free")
private BigDecimal tax_free;
@SerializedName("checksum")
private BigDecimal checksum;
@SerializedName("reason")
private String reason;
@SerializedName("refund_holder")
private String refund_holder;
@SerializedName("refund_bank")
private String refund_bank;
@SerializedName("refund_account")
private String refund_account;
@SerializedName("escrow_confirmed")
private boolean escrow_confirmed;
@SerializedName("extra")
private ExtraRequesterEntry extra;
public CancelData(String uid, boolean imp_uid_or_not) {
if ( imp_uid_or_not ) {
this.imp_uid = uid;
} else {
this.merchant_uid = uid;
}
}
public CancelData(String uid, boolean imp_uid_or_not, BigDecimal amount) {
this(uid, imp_uid_or_not);
this.amount = amount;
}
public void setTax_free(BigDecimal tax_free) {
this.tax_free = tax_free;
}
public void setChecksum(BigDecimal checksum) {
this.checksum = checksum;
}
public void setReason(String reason) {
this.reason = reason;
}
public void setRefund_holder(String refund_holder) {
this.refund_holder = refund_holder;
}
public void setRefund_bank(String refund_bank) {
this.refund_bank = refund_bank;
}
public void setRefund_account(String refund_account) {
this.refund_account = refund_account;
}
public void setEscrowConfirmed(boolean escrow_confirmed) {
this.escrow_confirmed = escrow_confirmed;
}
public ExtraRequesterEntry getExtra() {
return extra;
}
public void setExtra(ExtraRequesterEntry extra) {
this.extra = extra;
}
}
public IamportResponse<Payment> cancelPaymentByImpUid(CancelData cancelData) throws IamportResponseException, IOException {
AccessToken auth = getAuth().getResponse();
Call<IamportResponse<Payment>> call = this.iamport.cancel_payment(auth.getToken(), cancelData); //@@@@@@@이부분
Response<IamportResponse<Payment>> response = call.execute();
> if ( !response.isSuccessful() ) throw new IamportResponseException( getExceptionMessage(response), new HttpException(response) );
return response.body();
}
코드를 살펴보면 AccessToken을 받아주고 이 터큰을 cancel_payment 함수로 보내주는 것을 확인할 수 있다.(@@@@@@확인)
@POST("/payments/cancel")
Call<IamportResponse<Payment>> cancel_payment(
@Header("Authorization") String token,
@Body CancelData cancel_data
);
@POST라는 어노테이션을 첨 접해보지만 분석해보았을때, Hader에 token을 넣어주고, Body에 CancelData를 넣어주고 POST방식으로 해당 url로 요청을 보내는 과정이다!
HttpHeader, httpbody, httpEntity, restTemplate를 사용해서 오픈 api를 사용했던 방법이있었는데 지금 방법은 간단하고 효율적인 방법이였다. 어떤 방법이 좋고 효율적이고 어떤 방식을 사용해야하는지는 조금더 공부해야할 것 같다!
결제와 취소기능까지 구현해봤지만, 현재 결제기능과 취소기능은 내가 일일이 값을 스크립트문에 넣어줘서 진행을 했다. 이번에는 Order Entity에 있는 정보로 결제를 진행하고 취소해보자!