회사에서 카카오페이를 사용자사이트에 연결해달라는 요청이 들어왔다~
카카오페이를 연동하는 방법에 대해서 간략하게 일기를 쓸예정이다!
카카오페이 개발 참고페이지
- 카카오페이 개발API : https://developers.kakao.com/docs/latest/ko/kakaopay/common
- 카카오페이 데모 : https://developers.kakao.com/demo/pay/index
- 응답코드 : https://developers.kakao.com/docs/latest/ko/reference/rest-api-reference
필요한 디렉터리 파일
pages/buy/kakaopay/failPage.vue // 카카오페이 실패일 경우
pages/buy/kakaopay/index.vue // 승인요청에 대한 처리 로직, 클래스 등 필요한 로직 처리
pages/buy/index.vue // 결제페이지
api 로직 디렉터리
common/api/service/payment/kakaopay/KakaoPayApi.ts // 카카오페이 api 로직
common/api/service/payment/kakaopay/dto/kakaoPayReadyDto.ts // 카카오페이 준비 dto
common/api/service/payment/kakaopay/dto/kakaoPayCompleteDto.ts // 카카오페이 승인요청완료 dto
부수적으로 필요한 ts파일은 생략하겟습니당.!
먼저 결제 페이지인 pages/buy/index.ts 에서
주문대기 생성을 하여 order 쪽 백엔드쪽에서 요청을 실행하고 나면 주문번호가 생성이 된다.
그후에 카카오페이를 선택 했을 시에 일어나는 로직을 만들도록 하자!
buy/index.ts 에서 결제방법은 컴포넌트로 새로 만들어서 분기처리해준다.
components/views/buy/PaymentMethod.vue 에 결제방법 UI를 생성 시켜준다.
카카오페이에서 제공하는 이미지를 사용하여 해당 UI를 만들어준다.
회사 기획서에 맞게 페이에이전트타입으로 분리하여 그려준다.
대략 이런식으로 만든 후
pages/buy/index.vue 로 이동한다.
대기주문을 생성이 완료된후 로직을 탈수있게 코드를 짜준다.
proceedPayment 함수를 호출하면 아래와 같은 코드를 삽입
proceedPayment 안에 중간 내용 페이먼트 에이전트 타입에 따른 실행 코드 생성
setorderno를 만든이유는 카카오페이는 자신들의 페이지로 이동을 하고있다.
그러면 페이지간 이동이 일어나서 전에 내가 store 에서 저장했던 내용들이 날라간다.
그래서 그앞단인 buy 페이지에서 미리 세션스토리지에 값을 가지고 있기로 해서 위와 같이 만들었다.
카카오페이에서는 리다이렉트 url을 3개를 받고있는 이유이다. 아래 카카오페이에서 보여주는 예시를 보면
요런식으로 응답값을 준다. 위에 값은 우리회사 백엔드 개발자가 준다고 하니 나는 거기에 맞는 처리를 진행하였다.
하이브리드 웹이라서 웹뷰로 모바일까지 진행하고있다보니 안드로이드나 ios에 대한 처리는 따로 하지않았다!
회사 규격에 맞게 셋팅에 맞게 하면 된다고 보여진다.
kakaoPayReadyDto.ts
export interface kakaoPayReadyResponseDto{
tid: string;
// eslint-disable-next-line camelcase
next_redirect_pc_url: string;
// eslint-disable-next-line camelcase
next_redirect_app_url:string;
nextRedirectMobileUrl: string;
createdAt: string;
}
export interface kakaoPayApproveRequestDto{
tid: string;
orderId: string;
pgTocken: string;
totalAmount: number;
}
export interface kakaoPayReadyRequestDto {
orderNumber: string;
goodsName: string | undefined;
buyerName: string;
buyerPhone: string;
paymentAmount: string;
paymentMethod: string;
paymentAgentType: string;
approvalUrl: string;
cancelUrl: string | null;
failUrl:string | null;
quantity: number;
buyerEmail: string;
pointAmount: string;
couponNumber: string | null ;
}
export class kakaoPayReadyUtils {
static createDto(
orderNumber: string,
goodsName: string,
buyerName: string,
buyerPhone: string,
paymentAmount: string,
paymentMethod: string,
paymentAgentType: string,
approvalUrl: string,
cancelUrl: string | null,
failUrl: string | null,
quantity: number,
buyerEmail: string,
pointAmount: number,
couponNumber: string | null ,
) : kakaoPayReadyRequestDto{
return {
orderNumber,
goodsName,
buyerName,
buyerPhone,
paymentAmount,
paymentMethod,
paymentAgentType,
approvalUrl,
cancelUrl,
failUrl,
quantity,
buyerEmail,
pointAmount: pointAmount > 0 ? String(pointAmount) : '',
couponNumber,
};
}
static errMessage(errMsg: string){
let newStr = '';
switch (errMsg) {
case '400':
newStr = '요청한 파라미터값이 잘못되었습니다.';
break;
case '701':
newStr = '결제 인증이 완료되지 않았습니다.';
break;
case '702':
newStr = '이미 결제 완료된 결제건 입니다.';
break;
case '703':
newStr = '결제 포인트 금액이 잘못됐습니다.';
break;
case '704':
newStr = '결제 금액이 잘못됐습니다.';
break;
case '705':
newStr = '지원하지 않는 결제 수단입니다.';
break;
case '706':
newStr = '주문번호와 다른 값으로 API 출했습니다.';
break;
case '707':
newStr = '결제 준비 API에서 요청한 partner_user_id와 다른 값으로 결제승인 API 호출 했습니다.';
break;
case '708':
newStr = '잘못된 pg_token로 결제승인 API를 호출했습니다.';
break;
case '710':
newStr = '결제 취소 API 호출 시 취소 요청 금액을 취소 가능액보다 큰 금액으로 요청했습니다.';
break;
case '721':
newStr = 'TID가 존재하지 않습니다.';
break;
case '722':
newStr = '금액 정보가 잘못된 경우';
break;
case '723':
newStr = '결제 만료 시간이 지났습니디.';
break;
case '724':
newStr = '단건 결제 금액이 잘못되었습니다.';
break;
case '725':
newStr = '총 결제 금액이 잘못되었습니다.';
break;
case '726':
newStr = '주문 정보가 잘못되었습니다.';
break;
case '730':
newStr = '가맹점 앱 정보가 잘못되었습니다.';
break;
case '731':
newStr = 'CID 가 잘못됐습니다.';
break;
case '732':
newStr = 'GID 가 잘못됐습니다.';
break;
case '733':
newStr = 'CID_SECRET이 잘못됐습니다.';
break;
case '750':
newStr = 'SID가 존재하지 않습니다.';
break;
case '751':
newStr = '비활성화된 SID로 정기결제 API를 호출했습니다.';
break;
case '752':
newStr = 'SID가 월 최대 사용 회수를 초과했습니다.';
break;
case '753':
newStr = '정기 결제 API 호출 시 partner_user_id가 SID를 발급받았던 최초 결제 준비 API에서 요청한 값과 다릅니다.';
break;
case '761':
newStr = '입력한 전화번호가 카카오톡에 가입하지 않았습니다.';
break;
case '780':
newStr = '결제 승인 API 호출 실패';
break;
case '781':
newStr = '결제 취소 API 호출 실패';
break;
case '782':
newStr = '정기 결제 API 호출 실패';
break;
case '783':
newStr = '승인 요청을 할 수 없는 상태에서 결제 승인 API를 호출';
break;
case '784':
newStr = '취소 요청을 할 수 없는 상태에서 결제 취소 API를 호출';
break;
case '785':
newStr = '결제와 취소를 중복으로 요청';
break;
case '797':
newStr = '1회 결제 한도 금액을 초과했습니다.';
break;
case '798':
newStr = '허용되지 않는 IP를 사용하셧습니다.';
break;
case '799':
newStr = '등록된 웹사이트 도메인의 설정과 요청 도메인이 다릅니다.';
break;
}
return newStr;
}
}
kakaoPayApi.ts
카카오페이 요청 dto를 만들어주고 try 문에 다가
pc 일떄와 모바일때를 분리하여 백엔드에서 던져주는 url로 카카오페이로 페이지 이동을 시킨다~
사용자 페이지로 이동하여 현재까지 잘되었는지 테스트를 해본다!
아래는 카카오페이지 이동 후 화면이다.
위에 창도 잘뜨는건 확인 했으니 네트워크를 열어서 요청값들이 잘넘어갔는지도 확인을해준다.
내가 셋팅한것처럼 완료,실패,사용자취소 url 도 잘 셋팅이 되어서 넘어가있다!
사용자 취소 부분도 잘되는지 확인 해보자
우리 회사 기획서 상 결제페이지에 남아있지않도록하고 (중복결제에대한 문제점을 생각하셔서 그렇게 한것같다.) 매장이나 상품페이지로
이동하도록 기획을 하셧다.
위에 얼럿은 미리 내가 코딩을 해서 페이지 이동시 시스템 얼럿창이 뜨도록 설계를 하였다.
자 이제 승인요청 완료 부분이다!
kakaoPayCompleteDto.ts
export interface KakaoCompleteRequestDto {
tid: string | null;
orderNumber: string;
pgToken: string;
payType: string;
paymentMethod: string;
}
export class KakaoPayCompleteRequestDtoUtils {
static createDto(tidNo: string | null, orderNum: string, pgTokenStr: string, payTypeStr: string): KakaoCompleteRequestDto {
return {
orderNumber: orderNum,
tid: tidNo,
pgToken: pgTokenStr,
payType: payTypeStr,
paymentMethod: 'KAKAOPAY'
};
}
}
export interface KakaoCompleteResponseDto {
data: {
aid: string;
tid: string;
cid: string;
// eslint-disable-next-line camelcase
partner_order_id: string;
// eslint-disable-next-line camelcase
partner_user_id: string;
// eslint-disable-next-line camelcase
payment_method_type: string;
amount:{
total: number;
// eslint-disable-next-line camelcase
tax_free: number;
vat: number;
point: number;
discount: number;
// eslint-disable-next-line camelcase
green_deposit: number;
};
cardInfo:{
// eslint-disable-next-line camelcase
purchase_corp: string;
// eslint-disable-next-line camelcase
purchase_corp_code: string;
// eslint-disable-next-line camelcase
issuer_corp: string;
// eslint-disable-next-line camelcase
issuer_corp_code: string;
// eslint-disable-next-line camelcase
kakaopay_purchase_corp: string;
// eslint-disable-next-line camelcase
kakaopay_purchase_corp_code: string;
// eslint-disable-next-line camelcase
kakaopay_issuer_corp: string;
// eslint-disable-next-line camelcase
kakaopay_issuer_corp_code: string;
bin:string;
// eslint-disable-next-line camelcase
card_type: string;
// eslint-disable-next-line camelcase
install_month: string;
// eslint-disable-next-line camelcase
approved_id: string;
// eslint-disable-next-line camelcase
card_mid: string;
// eslint-disable-next-line camelcase
interest_free_install: string;
// eslint-disable-next-line camelcase
card_item_code: string;
};
}
}
2탄에서 계속!