기프티 프로젝트에서 결제 요청 기능을 구현 중이다.
처음에 REST API로 하는 줄 알고 API 문서를 신나게 읽어봤지만... 결제 후에 정보를 확인하는 부분만 API가 제공된다.
아임포트 github 에 가면 리액트로 client side에서 결제 요청하는 방법이 자세하게 안내되어 있다.
그러나 but 그대로 하면 타입스크립트에선 타입 에러가 난다.
<!-- jQuery -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
<!-- iamport.payment.js -->
<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.8.js"></script>
const IMP = window.IMP; // 생략 가능
IMP.init("{Merchant ID}"); // Example: imp00000000
가맹점 식별코드는 https://admin.iamport.kr에 관리자 체험 모드로 들어가면 받을 수 있다. 아마 관리자 체험 모드인 경우 모두 동일하게 iamport일 것 같다.
여기까지 하면 window의 property에 IMP가 없다고 타입스크립트 에러가 뜬다.
일단 아래처럼 any 타입으로 선언만 해줘도 에러는 잠재울 수 있다.
declare global {
interface Window {
IMP: any
}
}
그러나 타입스크립트 쓰면서 코드에 any가 보이면 아니 된다고 배웠습니다...
타입을 지정해 보자.
이 분 의 라이브러리를 참고했다. 다만 depreacated property 같은 것이 반영되지 않은 부분이 있어서 라이브러리를 사용하지는 않고, 따로 타입을 작성해 보았다.
IMP.request_pay 함수 호출시 첫번째 인자로 결제 데이터가 전달된다. 필수 항목은 결제 금액, pay method, merchant_uid이다. 결제 데이터 property는 아임포트 공식문서 에서 확인하여 결제 데이터 타입을 정의할 수 있다.
export interface RequestPayAdditionalParams {
digital?: boolean
vbank_due?: string
m_redirect_url?: string
app_scheme?: string
biz_num?: string
}
export interface Display {
card_quota?: number[]
}
export interface RequestPayParams extends RequestPayAdditionalParams {
pg?: string
pay_method: string
escrow?: boolean
merchant_uid: string
name?: string
amount: number
custom_data?: any
tax_free?: number
currency?: string
language?: string
buyer_name?: string
buyer_tel: string
buyer_email?: string
buyer_addr?: string
buyer_postcode?: string
notice_url?: string | string[]
display?: Display
}
결제 후 실행될 로직을 콜백 함수에 정의한다. 콜백 함수는 IMP.request_pay 함수의 두번째 인자이다. 콜백 함수의 인자인 결제 결과 객체 역시 아임포트 공식 문서에 자세히 안내되어 있다.
export interface RequestPayAdditionalResponse {
apply_num?: string
vbank_num?: string
vbank_name?: string
vbank_holder?: string | null
vbank_date?: number
}
export interface RequestPayResponse extends RequestPayAdditionalResponse {
success: boolean
error_code: string
error_msg: string
imp_uid: string | null
merchant_uid: string
pay_method?: string
paid_amount?: number
status?: string
name?: string
pg_provider?: string
pg_tid?: string
buyer_name?: string
buyer_email?: string
buyer_tel?: string
buyer_addr?: string
buyer_postcode?: string
custom_data?: any
paid_at?: number
receipt_url?: string
}
custom_data는 any이다...ㅂㄷㅂㄷ 결제사가 임의로 custom하는 object라고 되어 있어서 타입을 모르겠다.
인자들의 타입이 모두 결정되었으니 IMP 객체의 함수 타입을 정하고 IMP 객체를 Window의 property로 넣어주면 된다.
export type RequestPayResponseCallback = (response: RequestPayResponse) => void
export interface Iamport {
init: (accountID: string) => void
request_pay: (
params: RequestPayParams,
callback?: RequestPayResponseCallback,
) => void
}
declare global {
interface Window {
IMP?: Iamport
}
}
위에서 정의한 결제 데이터 객체와 콜백 함수를 인자로 넣어 IMP 객체의 request_pay 함수를 호출하면 끝!
const handlePayment = () => {
window.IMP?.init('iamport')
const amount: number =
priceSelections
.filter((price) => price.value === order.price)
.map((price) => price.amount)[0] || 0
if (!amount) {
alert('결제 금액을 확인해주세요')
return
}
const data: RequestPayParams = {
pg: 'html5_inicis',
pay_method: 'card',
merchant_uid: `mid_${new Date().getTime()}`,
amount: amount,
buyer_tel: '00-000-0000',
}
const callback = (response: RequestPayResponse) => {
const { success, merchant_uid, error_msg, imp_uid, error_code } = response
if (success) {
console.log(response)
} else {
console.log(response)
}
}
window.IMP?.request_pay(data, callback)
}
간단한 코드이지만 작성하면서 타입스크립트의 장점을 다시 한 번 느껴볼 수 있었다. amount 설정시에 number가 아닌 값이 들어올 수 있다고 타입 에러가 나서, amount가 0일 경우 결제창을 호출하지 않고 리턴하도록 에러 처리를 했다. 미처 생각하지 못한, 에러 처리를 해야할 만한 상황을 미리 체크해볼 수 있는 것이 장점인 것 같다.
오~~! 진행중이셨군요 !!