From 2174bf658b8f0863059fe7decb13f3f7ef6cb12f Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 14 Apr 2022 16:44:55 +0900 Subject: [PATCH 001/117] =?UTF-8?q?REST=20API=20Client=20v2=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + package.json | 8 +- src/bootpay.ts | 543 ++++++-------------------- src/lib/bootpay/singleton.ts | 17 - src/lib/bootpay/support.ts | 83 ---- src/lib/resource.ts | 137 +++++++ src/lib/response.ts | 184 +++++++++ test/access_token.js | 18 +- test/cancel.js | 24 -- test/cancelPayment.js | 19 + test/certificate.js | 23 +- test/destroySubscribeBillingKey.js | 14 + test/destroy_subscribe_billing_key.js | 17 - test/get_user_token.js | 19 - test/lookupSubscribeBilling.js | 14 + test/receiptPayment.js | 17 + test/remote_form.js | 34 -- test/requestUserToken.js | 16 + test/request_subscribe_rest.js | 29 -- test/subscribeCardPayment.js | 20 + test/subscribe_billing.js | 24 -- test/verify.js | 17 - tsconfig.json | 5 +- 23 files changed, 556 insertions(+), 727 deletions(-) delete mode 100644 src/lib/bootpay/singleton.ts delete mode 100644 src/lib/bootpay/support.ts create mode 100644 src/lib/resource.ts create mode 100644 src/lib/response.ts delete mode 100644 test/cancel.js create mode 100644 test/cancelPayment.js create mode 100644 test/destroySubscribeBillingKey.js delete mode 100644 test/destroy_subscribe_billing_key.js delete mode 100644 test/get_user_token.js create mode 100644 test/lookupSubscribeBilling.js create mode 100644 test/receiptPayment.js delete mode 100644 test/remote_form.js create mode 100644 test/requestUserToken.js delete mode 100644 test/request_subscribe_rest.js create mode 100644 test/subscribeCardPayment.js delete mode 100644 test/subscribe_billing.js delete mode 100644 test/verify.js diff --git a/.gitignore b/.gitignore index 4304d78..d1044a8 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ node_modules/* package-lock.json yarn.lock dist +test/requestSubscribeRest.js diff --git a/package.json b/package.json index 228aa34..f4ffbb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bootpay-backend-nodejs", - "version": "1.1.1", + "version": "2.0.0", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", @@ -10,11 +10,11 @@ "clear": "tsc --build --clean" }, "dependencies": { - "axios": "^0.21.1" + "axios": "^0.26.1" }, "devDependencies": { - "ts-node": "^10.2.1", - "typescript": "^4.4.3" + "ts-node": "^10.7.0", + "typescript": "^4.6.3" }, "repository": { "type": "git", diff --git a/src/bootpay.ts b/src/bootpay.ts index 7a8169a..225f454 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -1,517 +1,194 @@ -import { BootpaySingleton } from "./lib/bootpay/singleton" -import axios, { AxiosInstance, AxiosResponse, AxiosRequestConfig } from "axios" -import { isBlank, isPresent, objectKeyToUnderscore } from "./lib/bootpay/support" - -const API_URL: any = { - development: 'https://dev-api.bootpay.co.kr', - stage: 'https://stage-api.bootpay.co.kr', - production: 'https://api.bootpay.co.kr' -} - -export interface BootpayCommonResponse { - status: Number - code: Number - message?: String - data?: T -} - -export interface BootpayCancelData { - receiptId: string, // 부트페이에서 발급한 영수증 id - price?: number, // (선택사항) 부분취소 요청시 금액을 지정, 미지정시 전액 취소 (부분취소가 가능한 PG사, 결제수단에 한해 적용됨) - name?: string, // 취소 요청자 이름 - reason?: string, // 취소 요청 사유 - refund?: BootpayRefundData -} - -export interface BootpayRefundData { - account: string, - accountholder: string, - bankcode: string -} - -export interface BootpaySubscribeBillingData { - orderId: string, // 개발사에서 지정하는 고유주문번호 - pg: string, // PG사의 Alias ex) danal, kcp, inicis 등 - itemName: string, // 상품명 - cardNo: string, // 카드 일련번호 - cardPw: string, // 카드 비밀번호 앞 2자리 - expireYear: string, // 카드 유효기간 년 - expireMonth: string, // 카드 유효기간 월 - identifyNumber: string, // 주민등록번호 또는 사업자번호 - userInfo?: BootpayUserInfoData, // 구매자 정보 - extra?: BootpaySubscribeExtraData //기타 옵션 -} - -export interface BootpayRequestSubscribeBillingPaymentData { - billingKey: string, // 발급받은 빌링키 - itemName: string, // 결제할 상품명 - price: number, // 결제할 상품금액 - taxFree?: number, // 면세금액 - orderId: string, // 개발사에서 지정하는 고유주문번호 - quota?: number, // 할부 개월수 - interest?: number, // 무이자 여부 ( 웰컴 페이먼츠만 가능 ) - userInfo?: BootpayUserInfoData, // 구매자 정보, 특정 PG사의 경우 구매자 휴대폰 번호를 필수로 받는다 - items?: Array, // 구매할 상품정보 - feedbackUrl?: string, // 결제 완료 후 피드백 받을 URL - feedbackContentType?: string, // Feedback 받을 경우 content-type - json, urlencoded - extra?: BootpaySubscribeExtraData//기타 옵션 -} - -export interface BootpayReserveSubscribeBillingData { - billingKey: string, // 발급받은 빌링키 - itemName: string, // 결제할 상품명 - price: number, // 결제할 상품금액 - taxFree: number, // 면세금액 - orderId: string, // 개발사에서 지정하는 고유주문번호 - quota?: number, // 할부 개월수 - interest?: number, // 무이자 여부 ( 웰컴 페이먼츠만 가능 ) - schedulerType: string, // 실행 방법 - oneshot - executeAt: number, // (예약) 결제 실행시간 - userInfo?: BootpayUserInfoData, // 구매자 정보, 특정 PG사의 경우 구매자 휴대폰 번호를 필수로 받는다 - items?: Array, // 구매할 상품정보 - feedbackUrl: string, // 결제 완료 후 피드백 받을 URL - feedbackContentType: string, // Feedback 받을 경우 content-type - json, urlencoded - extra?: BootpaySubscribeExtraData//기타 옵션 -} - -export interface BootpayRequestPaymentData { - pg?: string, // [PG 결제] 사용하고자 하는 PG사의 Alias를 입력. ex) danal, kcp, inicis등, 미 지정시 통합결제창이 오픈 - method?: string, //card:카드, phone: 휴대폰, bank: 실시간 계좌이체, vbank: 가상계좌, auth: 본인인증, card_rebill: 정기결제, easy: 카카오,페이코,네이버페이 등의 간편결제, 미지정시 통합결제창 오픈 - methods?: Array, // 통합결제시 사용할 method 배열 형태 - orderId: string, // 개발사에서 관리하는 고유결제번호 - price: number, // 결제금액 - taxFree?: number, // 비과세 금액 - itemName: string, // 결제할 상품명 - // returnUrl?: string, - params?: string, // string 형태로 전달 할 값, 결제 후 똑같이 리턴해드림 - userInfo?: BootpayUserInfoData, // 구매자 정보 - items?: Array, // 상품정보 - extra?: any // 기타 옵션 -} - -export interface BootpayRequestUserTokenData { - userId: string, // 개발사에서 관리하는 회원 고유 id - email?: string, // 회원 email - name?: string, // 회원명 - gender?: number, // 0 - 여자, 1 - 남자 - birth?: string, // 생일 901004 - phone?: string //01012341234 -} - -export interface BootpayItemData { - unique: string, // 상품 고유키 - qty: number, // 수량 - itemName: string, // 상품명 - price: number, // 상품단가 - cat1?: string, // 카테고리 상 - cat2?: string, // 카테고리 중 - cat3?: string // 카테고리 하 -} - -export interface BootpaySubscribeExtraData { - subscribeTestPayment: number, // 100원 결제 후 결제가 되면 billing key를 발행, 결제가 실패하면 에러 - rawData?: number //PG 오류 코드 및 메세지까지 리턴 -} - -export interface BootpayUserInfoData { - id: string, // 개발사에서 관리하는 회원 고유 id - username?: string, //구매자 이름 - email?: string, // 구매자 email - phone?: string, //01012341234 - gender?: number, //0:여자, 1:남자 - area?: string, // 서울|인천|대구|광주|부산|울산|경기|강원|충청북도|충북|충청남도|충남|전라북도|전북|전라남도|전남|경상북도|경북|경상남도|경남|제주|세종|대전 중 택 1 - birth?: string -} - - -class BootpayRestClient extends BootpaySingleton { - - $http: AxiosInstance - $token?: string - applicationId?: string - privateKey?: string - mode: string - +import { BootpayBackendNodejsResource } from './lib/resource' +import { + AccessTokenResponseParameters, + CancelPaymentParameters, + CertificateResponseParameters, + DestroySubscribeResponseParameters, + ReceiptResponseParameters, + SubscriptionBillingRequestParameters, + SubscriptionBillingResponseParameters, + SubscriptionCardPaymentRequestParameters, UserTokenRequestParameters, UserTokenResponseParameters +} from './lib/response' + +class BootpayBackendNodejs extends BootpayBackendNodejsResource { constructor() { super() - this.mode = 'production' - this.$token = undefined - this.$http = axios.create({ - timeout: 60000 - }) - this.$http.interceptors.response.use((response: AxiosResponse): any => { - if (isPresent(response.request) && isPresent(response.headers)) { - return response.data as BootpayCommonResponse - } else { - return { - code: -100, - status: 500, - message: `오류가 발생했습니다. ${ response }`, - data: response - } as BootpayCommonResponse - } - }, function (error) { - if (isPresent(error.response)) { - return Promise.reject(error.response.data) - } else { - return Promise.reject({ - code: -100, - message: `통신오류가 발생하였습니다. ${ error.message }`, - status: 500 - }) - } - }) - this.$http.interceptors.request.use((config: AxiosRequestConfig) => { - if (isPresent(this.$token)) { - config.headers.authorization = this.$token - } - config.headers['Content-Type'] = 'application/json' - config.headers['Accept'] = 'application/json' - return config - }, (error) => { - return Promise.reject(error) - }) } /** - * rest api configure - * Comment by rumi - * @date: 2020-10-27 - * @param (applicationId, privateKey, mode) - * @returns void + * Get Access Token + * Comment by GOSOMI + * @date: 2022-04-12 + * @param + * @returns */ - setConfig(applicationId: string, privateKey: string, mode: string = 'production') { - this.applicationId = applicationId - this.privateKey = privateKey - this.mode = isPresent(mode) ? mode : 'production' - if (isBlank(API_URL[this.mode])) { - throw new Error(`환경설정 설정이 잘못되었습니다. 현재 설정된 모드: ${ this.mode }, 가능한 모드: development, stage, production`) - } - return - } - - /** - * 1. 토큰 발급 - * getting access token - * Comment by rumi - * @date: 2020-10-27 - * @param void - * @returns Promise - */ - async getAccessToken(): Promise { - let response: BootpayCommonResponse - try { - response = await this.$http.post( - this.getApiUrl('request/token'), - { - application_id: this.applicationId, - private_key: this.privateKey - } - ) - } catch (e) { - return Promise.reject(e) - } - this.$token = response.data.token - return Promise.resolve(response) - } - - /** - * 2. 결제 검증 - * receipt verify - * Comment by rumi - * @date: 2020-10-27 - * @param receiptId - * @returns Promise - */ - async verify(receiptId: string): Promise { - let response: BootpayCommonResponse - try { - response = await this.$http.get( - this.getApiUrl(`receipt/${ receiptId }`) - ) - } catch (e) { - return Promise.reject(e) - } - return Promise.resolve(response) - } - - /** - * 3. 결제 취소 (전액 취소 / 부분 취소) - * Payment Cancel - * Comment by rumi - * @date: 2020-10-27 - * @param data: BootpayCancelData - * @returns Promise - */ - async cancel(data: BootpayCancelData) { - let response: BootpayCommonResponse + async getAccessToken(): Promise { try { - response = await this.$http.post( - this.getApiUrl('cancel'), - { - receipt_id: data.receiptId, - price: data.price, - name: data.name, - reason: data.reason, - refund: data.refund - } - ) - } catch (e) { + const { application_id, private_key } = this.bootpayConfiguration + const response: AccessTokenResponseParameters = await this.post('request/token', { + application_id, + private_key + }) + // set Token + this.setToken(response.access_token) + return Promise.resolve(response) + } catch (e: any) { return Promise.reject(e) } - return Promise.resolve(response) } /** - * 4. 빌링키 발급 - * Request Subscribe Card Billing Key - * Comment by rumi - * @date: 2020-10-27 - * @param data: BootpaySubscribeBillingData - * @returns Promise + * Lookup Receipt + * Comment by GOSOMI + * @date: 2022-04-13 + * @param receiptId: string */ - async requestSubscribeBillingKey(data: BootpaySubscribeBillingData) { - let response: BootpayCommonResponse + async receiptPayment(receiptId: string): Promise { try { - response = await this.$http.post( - this.getApiUrl('request/card_rebill'), - { - order_id: data.orderId, - pg: data.pg, - item_name: data.itemName, - card_no: data.cardNo, - card_pw: data.cardPw, - expire_year: data.expireYear, - expire_month: data.expireMonth, - identify_number: data.identifyNumber, - user_info: data.userInfo, - extra: objectKeyToUnderscore(data.extra) - } - ) + const response: ReceiptResponseParameters = await this.get(`receipt/${ receiptId }`) + return Promise.resolve(response) } catch (e) { return Promise.reject(e) } - return Promise.resolve(response) } - /** - * 4-1. 발급된 빌링키로 결제 승인 요청 - * subscribe payment by billing key - * Comment by rumi - * @date: 2020-10-27 - * @param data: BootpayRequestSubscribeBillingPaymentData - * @returns Promise + * Cancel Payment + * Comment by GOSOMI + * @date: 2022-04-13 + * @param cancelPayment: CancelPaymentParameters + * @returns Promise */ - async requestSubscribeBillingPayment(data: BootpayRequestSubscribeBillingPaymentData) { - let response: BootpayCommonResponse + async cancelPayment(cancelPayment: CancelPaymentParameters): Promise { try { - response = await this.$http.post( - this.getApiUrl('subscribe/billing'), - { - billing_key: data.billingKey, - order_id: data.orderId, - item_name: data.itemName, - price: data.price, - tax_free: data.taxFree, - interest: data.interest, - quota: data.quota, - items: objectKeyToUnderscore(data.items), - user_info: objectKeyToUnderscore(data.userInfo), - feedback_url: data.feedbackUrl, - feedback_content_type: data.feedbackContentType, - extra: data.extra - } - ) + const response: ReceiptResponseParameters = await this.post('cancel', { + ...cancelPayment + }) + return Promise.resolve(response) } catch (e) { return Promise.reject(e) } - return Promise.resolve(response) } /** - * 4-2. 발급된 빌링키로 결제 예약 요청 - * reserve payment by billing key - * Comment by rumi - * @date: 2020-10-27 - * @param data: BootpayReserveSubscribeBillingData - * @returns Promise + * Lookup Certificate Data + * Comment by GOSOMI + * @date: 2022-04-14 + * @param receiptId: string + * @returns Promise */ - async reserveSubscribeBilling(data: BootpayReserveSubscribeBillingData) { - let response: BootpayCommonResponse + async certificate(receiptId: string): Promise { try { - response = await this.$http.post( - this.getApiUrl('subscribe/billing/reserve'), - { - billing_key: data.billingKey, - order_id: data.orderId, - price: data.price, - tax_free: data.taxFree, - user_info: objectKeyToUnderscore(data.userInfo), - item_info: objectKeyToUnderscore(data.items), - item_name: data.itemName, - feedback_url: data.feedbackUrl, - feedback_content_type: data.feedbackContentType, - scheduler_type: data.schedulerType, - execute_at: data.executeAt - } - ) + const response: CertificateResponseParameters = await this.get(`certificate/${ receiptId }`) + return Promise.resolve(response) } catch (e) { return Promise.reject(e) } - return Promise.resolve(response) } /** - * 4-2-1. 발급된 빌링키로 결제 예약 - 취소 요청 - * Cancel Reserve Subscribe Billing - * Comment by rumi - * @date: 2020-10-27 - * @param reserveId: string - * @returns Promise + * ConfirmPayment + * Comment by GOSOMI + * @date: 2022-04-14 + * @param receiptId: string + * @returns Promise */ - async destroyReserveSubscribeBilling(reserveId: string) { - let response: BootpayCommonResponse + async confirmPayment(receiptId: string): Promise { try { - response = await this.$http.delete( - this.getApiUrl(`subscribe/billing/reserve/${ reserveId }`) - ) + const response: ReceiptResponseParameters = await this.post('confirm', { + receipt_id: receiptId + }) + return Promise.resolve(response) } catch (e) { return Promise.reject(e) } - return Promise.resolve(response) } - /** - * 4-3. 빌링키 삭제 - * destroy billing key - * Comment by rumi - * @date: 2020-10-27 - * @param billingKey: string - * @returns Promise + * lookupSubscribeBillingKey + * Comment by GOSOMI + * @date: 2022-04-14 + * @param receiptId: string + * @returns Promise */ - async destroySubscribeBillingKey(billingKey: string) { - let response: BootpayCommonResponse + async lookupSubscribeBillingKey(receiptId: string): Promise { try { - response = await this.$http.delete( - this.getApiUrl(`subscribe/billing/${ billingKey }`) - ) + const response: SubscriptionBillingResponseParameters = await this.get(`subscribe/billing_key/${ receiptId }`) + return Promise.resolve(response) } catch (e) { return Promise.reject(e) } - return Promise.resolve(response) } - /** - * 5. (부트페이 단독 - 간편결제창, 생체인증 기반의 사용자를 위한) 사용자 토큰 발급 - * get user token - * Comment by rumi - * @date: 2020-10-27 - * @param data: BootpayRequestUserTokenData - * @returns Promise + * requestSubscribeBillingKey + * Comment by GOSOMI + * @date: 2022-04-14 + * @param subscriptionBillingRequest: SubscriptionBillingRequestParameters + * @returns Promise */ - async requestUserToken(data: BootpayRequestUserTokenData) { - let response: BootpayCommonResponse + async requestSubscribeBillingKey(subscriptionBillingRequest: SubscriptionBillingRequestParameters): Promise { try { - response = await this.$http.post( - this.getApiUrl('request/user/token'), - { - user_id: data.userId, - email: data.email, - name: data.name, - gender: data.gender, - birth: data.birth, - phone: data.phone - } - ) + const response: SubscriptionBillingResponseParameters = await this.post('request/subscribe', { + ...subscriptionBillingRequest + }) + return Promise.resolve(response) } catch (e) { return Promise.reject(e) } - return Promise.resolve(response) } /** - * 6. 결제링크 생성 - * REST API로 결제 요청을 합니다 - * Comment by rumi - * @date: 2020-10-27 - * @param data: any - * @returns Promise + * requestSubscribeCardPayment + * Comment by GOSOMI + * @date: 2022-04-14 + * @param subscriptionCardRequest: SubscriptionCardPaymentRequestParameters + * @returns Promise */ - async requestPayment(data: BootpayRequestPaymentData) { - let response: BootpayCommonResponse + async requestSubscribeCardPayment(subscriptionCardRequest: SubscriptionCardPaymentRequestParameters): Promise { try { - response = await this.$http.post( - this.getApiUrl('request/payment'), - { - pg: data.pg, - method: data.method, - methods: data.methods, - order_id: data.orderId, - price: data.price, - params: data.params, - tax_free: data.taxFree, - name: data.itemName, - user_info: objectKeyToUnderscore(data.userInfo), - items: objectKeyToUnderscore(data.items), - // return_url: data.returnUrl, - extra: objectKeyToUnderscore(data.extra) - } - ) + const response: ReceiptResponseParameters = await this.post('subscribe/payment', { + ...subscriptionCardRequest + }) + return Promise.resolve(response) } catch (e) { return Promise.reject(e) } - return Promise.resolve(response) } - /** - * 7. 서버 승인 요청 - * Server Submit method - * Comment by rumi - * @date: 2020-10-27 - * @param receiptId - * @returns Promise + * destroyBillingKey + * Comment by GOSOMI + * @date: 2022-04-14 + * @param billingKey:string + * @returns Promise */ - async submit(receiptId: string): Promise { - let response: BootpayCommonResponse + async destroyBillingKey(billingKey: string): Promise { try { - response = await this.$http.post( - this.getApiUrl('submit'), - { receipt_id: receiptId } - ) + const response: DestroySubscribeResponseParameters = await this.delete(`subscribe/billing_key/${ billingKey }`) + return Promise.resolve(response) } catch (e) { return Promise.reject(e) } - return Promise.resolve(response) } - /** - * 8. 본인 인증 결과 검증 - * Certificate Data - * Comment by rumi - * @date: 2020-10-27 - * @param receiptId: string - * @returns Promise + * requestUserToken + * Comment by GOSOMI + * @date: 2022-04-14 + * @param userTokenRequest:UserTokenRequestParameters + * @returns Promise */ - async certificate(receiptId: string) { - let response: BootpayCommonResponse + async requestUserToken(userTokenRequest: UserTokenRequestParameters): Promise { try { - response = await this.$http.get( - this.getApiUrl(`certificate/${ receiptId }`) - ) + const response: UserTokenResponseParameters = await this.post('request/user/token', { + ...userTokenRequest + }) + return Promise.resolve(response) } catch (e) { return Promise.reject(e) } - return Promise.resolve(response) - } - - private getApiUrl(uri: string) { - return [API_URL[this.mode], uri].join('/') } } -export const Bootpay = BootpayRestClient.currentInstance() \ No newline at end of file +export const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() \ No newline at end of file diff --git a/src/lib/bootpay/singleton.ts b/src/lib/bootpay/singleton.ts deleted file mode 100644 index eb14325..0000000 --- a/src/lib/bootpay/singleton.ts +++ /dev/null @@ -1,17 +0,0 @@ -export class BootpaySingleton { - private static __instance: any - - /** - * Singleton Instance Return - * Comment by rumi - * @date: 2020-10-20 - * @param - * @returns - */ - static currentInstance() { - if (!this.__instance) { - this.__instance = new this() - } - return this.__instance as T - } -} \ No newline at end of file diff --git a/src/lib/bootpay/support.ts b/src/lib/bootpay/support.ts deleted file mode 100644 index b803a8a..0000000 --- a/src/lib/bootpay/support.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { BootpaySingleton } from "./singleton" - -export interface Validate { - isBlank(value: any): Boolean - - isType(value: any, type: string): Boolean - - isPresent(value: any): Boolean - - presence(value: any, defaultValue: any): any - - toUnderscore(value: any): any - - objectKeyToUnderscore(value: any): any -} - -class ValidateMethod extends BootpaySingleton implements Validate { - isBlank(value: any): Boolean { - let valid: Boolean = false - if (typeof value === 'string') { - valid = value.length === 0 - } else if (Array.isArray(value)) { - valid = value.length === 0 - } else { - valid = value === undefined || - value === null || - (this.isType(value, 'object') && value.constructor === Object && Object.keys(value).length === 0) - } - return valid - } - - isType(value: any, type: string) { - return (typeof value === type) - } - - isPresent(value: any): Boolean { - return !this.isBlank(value) - } - - presence(value: any, defaultValue: any): any { - if (this.isBlank(value)) { - return defaultValue - } else { - return value - } - } - - objectKeyToUnderscore(value: any): any { - let cloneObject: any = undefined - if (isPresent(value)) { - const _this = this - if (Array.isArray(value)) { - cloneObject = [] - value.forEach((_value) => { - let childObject: any = {} - Object.keys(_value).forEach((key) => { - childObject[_this.toUnderscore(key)] = _value[key] - }) - cloneObject.push(childObject) - }) - } else { - cloneObject = {} - Object.keys(value).forEach((key) => { - cloneObject[_this.toUnderscore(key)] = value[key] - }) - } - } - return cloneObject - } - - toUnderscore(value: any): any { - return value.split(/(?=[A-Z])/).join('_').toLowerCase() - } -} - -const ValidClass: ValidateMethod = ValidateMethod.currentInstance() - -export const isPresent = (value: any) => ValidClass.isPresent(value) -export const isBlank = (value: any) => ValidClass.isBlank(value) -export const presence = (value: any, defaultValue: any) => ValidClass.presence(value, defaultValue) -export const toUnderscore = (value: any) => ValidClass.toUnderscore(value) -export const objectKeyToUnderscore = (value: any) => ValidClass.objectKeyToUnderscore(value) -export const isType = (value: any, type: string) => ValidClass.isType(value, type) \ No newline at end of file diff --git a/src/lib/resource.ts b/src/lib/resource.ts new file mode 100644 index 0000000..85a1f24 --- /dev/null +++ b/src/lib/resource.ts @@ -0,0 +1,137 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' + +export interface BootpayRestApiErrorResponse { + error_code?: number + pg_error_code?: number + message?: string +} + +interface BootpayEntrypoints { + development: string + stage: string + production: string +} + +interface BootpayConfiguration { + application_id: string + private_key: string + mode: 'development' | 'production' | 'stage' +} + +export class BootpayBackendNodejsResource { + $http: AxiosInstance + $token?: string + mode: string + bootpayConfiguration: BootpayConfiguration + API_ENTRYPOINTS: BootpayEntrypoints + + constructor() { + this.mode = 'production' + this.$http = axios.create({ + timeout: 60000 + }) + this.$token = undefined + this.bootpayConfiguration = { + application_id: '', + private_key: '', + mode: 'production' + } + this.API_ENTRYPOINTS = { + development: 'https://dev-api.bootpay.co.kr/v2', + stage: 'https://stage-api.bootpay.co.kr/v2', + production: 'https://api.bootpay.co.kr/v2' + } + this.$http.interceptors.response.use((response: AxiosResponse): any => { + if (response.request !== undefined && response.headers !== undefined) { + return response.data + } else { + // 오류를 리턴 + return response.data as BootpayRestApiErrorResponse + } + }, (error) => { + if (error.response !== undefined) { + return Promise.reject(error.response.data as BootpayRestApiErrorResponse) + } else { + return Promise.reject({ + error_code: -100, + message: `Request Rest Api Failed to Bootpay Server, ${ error.message }` + }) as BootpayRestApiErrorResponse + } + }) + this.$http.interceptors.request.use((config: AxiosRequestConfig) => { + if (config.headers !== undefined) { + if (this.$token !== undefined) { + config.headers.authorization = `Bearer ${ this.$token }` + } + config.headers['Content-Type'] = 'application/json' + config.headers['Accept'] = 'application/json' + } + return config + }, (error) => { + return Promise.reject(error) + }) + } + + /** + * Environments + * Comment by GOSOMI + * @date: 2022-04-12 + * @param configuration: BootpayConfiguration + * @returns void + */ + setConfiguration(configuration: BootpayConfiguration): void { + if (configuration.mode === undefined) { + configuration.mode = 'production' + } + this.bootpayConfiguration = configuration + } + + /** + * Set Access Token + * Comment by GOSOMI + * @date: 2022-04-12 + */ + setToken(token: string): void { + this.$token = token + } + + entrypoints(url: string): string { + return [this.API_ENTRYPOINTS[this.bootpayConfiguration.mode], url].join('/') + } + + async get(url: string, config?: AxiosRequestConfig): Promise { + try { + const response: T = await this.$http.get(this.entrypoints(url), config) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + + async post(url: string, data?: D, config?: AxiosRequestConfig): Promise { + try { + const response: T = await this.$http.post(this.entrypoints(url), data, config) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + + async put(url: string, data?: D, config?: AxiosRequestConfig): Promise { + try { + const response: T = await this.$http.put(this.entrypoints(url), data, config) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + + async delete(url: string, config?: AxiosRequestConfig): Promise { + try { + const response: T = await this.$http.delete(this.entrypoints(url), config) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } +} \ No newline at end of file diff --git a/src/lib/response.ts b/src/lib/response.ts new file mode 100644 index 0000000..d56d3ba --- /dev/null +++ b/src/lib/response.ts @@ -0,0 +1,184 @@ +export interface AccessTokenResponseParameters { + expire_in: number + access_token: string +} + +export interface ReceiptResponseParameters { + receipt_id: string + order_id: string + price: number + tax_free: number + cancelled_price: number + cancelled_tax_free: number + order_name: string + company_name: string + gateway_url: string + metadata: string + sandbox: boolean + pg: string + method: string + method_origin: string + purchased_at?: Date + requested_at: Date + cancelled_at?: Date + escrow_status?: string + status: number + card_data?: CardData, + phone_data?: PhoneData, + bank_data?: BankData + vbank_data?: BankData +} + +export interface ExtraModel { + subscribe_test_payment: boolean +} + +export interface UserModel { + id: string + username: string + phone: string + email: string +} + +export interface ItemModel { + id: string + name: string + qty: number + price: number +} + +export interface BillingData { + card_no: string + card_company: string + card_company_code: string + card_type: string + card_hash: string +} + +export interface CardData { + tid: string + card_approve_no: string + card_no: string + card_quota: string + card_company_code: string + card_company: string + card_interest: string + receipt_url: string + card_type?: string + card_owner_type?: string + point?: number +} + +export interface PhoneData { + tid: string + auth_no?: string + phone?: string +} + +export interface BankData { + tid: string + bank_code: string + bank_name: string + back_username: string + bank_account?: string + sender_name?: string + expired_at?: Date + cash_receipt_tid?: string + cash_receipt_type?: string + cash_receipt_no?: string +} + +export interface CancelPaymentParameters { + receipt_id: string + cancel_price?: number + cancel_tax_free?: number + cancel_id?: string + cancel_username?: string + cancel_message?: string + refund: Refund +} + +export interface Refund { + bank_account: string + back_username: string + bank_code: string +} + +export interface CertificateResponseParameters { + receipt_id: string + authenticate_id: string + authenticated_at: Date + status: number + authenticate_data: AuthenticateData +} + +export interface AuthenticateData { + phone?: string + unique: string + birth: Date + gender: number + foreigner?: number + carrier?: string + tid: string +} + +export interface SubscriptionBillingRequestParameters { + pg: string + order_name: string + subscription_id: string + card_no: string + card_pw: string + card_identity_no: string + card_expire_year: string + card_expire_month: string + price: number + tax_free: number + extra: ExtraModel + user: UserModel + metadata: object +} + +export interface SubscriptionBillingResponseParameters { + billing_key: string + billing_data: BillingData + receipt_id: string + subscription_id: string + metadata: object + pg: string + method: string + published_at: Date + requested_at: Date + receipt_Data: ReceiptResponseParameters + billing_expire_at: Date +} + +export interface SubscriptionCardPaymentRequestParameters { + billing_key: string + order_name: string + price: number + tax_free: number + card_quota?: string + card_interest?: string + order_id: string + items?: Array + user?: UserModel + extra?: ExtraModel +} + +export interface DestroySubscribeResponseParameters { + billing_key: string +} + +export interface UserTokenRequestParameters { + user_id: string + email?: string + username?: string + gender?: number + birth?: string + phone?: string +} + +export interface UserTokenResponseParameters { + user_token: string + expired_at: Date +} \ No newline at end of file diff --git a/test/access_token.js b/test/access_token.js index 7eb3931..af55542 100644 --- a/test/access_token.js +++ b/test/access_token.js @@ -1,20 +1,16 @@ -// import { BootpayRestClient } from 'bootpay-backend-nodejs' -// const Bootpay = require('bootpay-backend-nodejs') +// import { Bootpay } from "../dist/bootpay" (async () => { - const Bootpay = require('../dist/bootpay').Bootpay - // const Bootpay = require('bootpay-backend-nodejs').Bootpay - // const Bootpay = require('bootpay-backend-nodejs') - - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) try { let response = await Bootpay.getAccessToken() console.log(response) - } catch(e) { + } catch (e) { console.log(e) } })() diff --git a/test/cancel.js b/test/cancel.js deleted file mode 100644 index 937b2f8..0000000 --- a/test/cancel.js +++ /dev/null @@ -1,24 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay').Bootpay - - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.cancel({ - receiptId: '5b0df1b8e13f332c6c83df6a', - price: 1000, - name: '취소자명', - reason: '취소합니다' - }) - } catch (e) { - console.log(e) - return - } - console.log(response) - } -})() \ No newline at end of file diff --git a/test/cancelPayment.js b/test/cancelPayment.js new file mode 100644 index 0000000..a97dbeb --- /dev/null +++ b/test/cancelPayment.js @@ -0,0 +1,19 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.cancelPayment({ + receipt_id: '625763921fc19202e4747199', + cancel_price: 1000, + cancel_username: '테스트 사용자', + cancel_message: '테스트 취소입니다.' + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/certificate.js b/test/certificate.js index 2e6162f..f4ba74f 100644 --- a/test/certificate.js +++ b/test/certificate.js @@ -1,17 +1,14 @@ (async () => { - const RestClient = require('../dist/bootpay').Bootpay - RestClient.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await RestClient.getAccessToken() - if (token.status === 200) { - let response - try { - response = await RestClient.certificate('1234') - } catch (e) { - return console.log(e) - } + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.certificate('625783a6cf9f6d001d0aed19') console.log(response) + } catch (e) { + console.log(e) } })() \ No newline at end of file diff --git a/test/destroySubscribeBillingKey.js b/test/destroySubscribeBillingKey.js new file mode 100644 index 0000000..31b943d --- /dev/null +++ b/test/destroySubscribeBillingKey.js @@ -0,0 +1,14 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.destroyBillingKey('62579f4bcf9f6d001d0aed21') + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/destroy_subscribe_billing_key.js b/test/destroy_subscribe_billing_key.js deleted file mode 100644 index 2eb9b20..0000000 --- a/test/destroy_subscribe_billing_key.js +++ /dev/null @@ -1,17 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.destroySubscribeBillingKey('5f97b8a40f606f03e8ab32a0') - } catch (e) { - return console.log(e) - } - console.log(response) - } -})() \ No newline at end of file diff --git a/test/get_user_token.js b/test/get_user_token.js deleted file mode 100644 index 5fef3ed..0000000 --- a/test/get_user_token.js +++ /dev/null @@ -1,19 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - const token = await Bootpay.getAccessToken() - if (token.status === 200) { - let result - try { - result = await Bootpay.requestUserToken({ - userId: 'gosomi' - }) - } catch (e) { - return console.log(e) - } - console.log(result) - } -})() \ No newline at end of file diff --git a/test/lookupSubscribeBilling.js b/test/lookupSubscribeBilling.js new file mode 100644 index 0000000..ac7a2be --- /dev/null +++ b/test/lookupSubscribeBilling.js @@ -0,0 +1,14 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.lookupSubscribeBillingKey('6257989ecf9f6d001d0aed1b') + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/receiptPayment.js b/test/receiptPayment.js new file mode 100644 index 0000000..86e8a71 --- /dev/null +++ b/test/receiptPayment.js @@ -0,0 +1,17 @@ +// import { Bootpay } from "../dist/bootpay" + + +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.receiptPayment('61b009aaec81b4057e7f6ecd') + console.log(response) + } catch (e) { + console.log(e) + } +})() diff --git a/test/remote_form.js b/test/remote_form.js deleted file mode 100644 index 937a4dd..0000000 --- a/test/remote_form.js +++ /dev/null @@ -1,34 +0,0 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - -BootpayRest.remoteForm( - { - pg: 'danal', - fm: ['card', 'phone'], - n: '테스트 결제', //상품명 - o_key: 'unique_value_1234', //가맹점의 상품 고유 키 - is_r_n: false, //구매자가 상품명 입력 허용할지 말지 - is_r_p: false, //구매자가 가격 입력 허용할지 말지 - is_addr: false, //주소창 추가 할지 말지 - is_da: false, //배송비 추가 할지 말지 - is_memo: false, //구매자로부터 메모를 받을 지 - tfp: 0, //비과세 금액 - ip: 10000, //아이템 판매금액 - dp: 0, //디스플레이용 가격, 할인전 가격을 의미함, 쿠폰이나 프로모션에 의한 가격 디스카운트 개념 필요 - 페이코 때문에 생긴 개념 - dap: 0, //기본배송비 - dap_jj: 0, //제주 배송비 - dap_njj: 0 //제주 외 지역 도서산간 추가비용 - }, - { -// # st: 1, #1: sms, 2:lms, 3:mms, 4:알림톡, 5:친구톡 -// # rps: ['010-1234-5678', '010-1111-2222'], # 받는 사람 전화번호 -// # sp: '010-1234-1111', # 보내는 사람 전화번호 -// # msg: '테스트 문자입니다' - } -).then(function (data) { - console.log(data); -}); - diff --git a/test/requestUserToken.js b/test/requestUserToken.js new file mode 100644 index 0000000..b80c1c1 --- /dev/null +++ b/test/requestUserToken.js @@ -0,0 +1,16 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestUserToken({ + user_id: 'gosomi1' + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/request_subscribe_rest.js b/test/request_subscribe_rest.js deleted file mode 100644 index 4f5b744..0000000 --- a/test/request_subscribe_rest.js +++ /dev/null @@ -1,29 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.requestSubscribeBillingKey({ - orderId: (new Date()).getTime(), - pg: 'nicepay', - itemName: '정기결제 30일권', - cardNo: '[ 카드 번호 ]', - cardPw: '[ 카드 비밀번호 앞 2자리 ]', - expireYear: '[ 카드 만료 연도 ]', - expireMonth: '[ 카드 만료 월 ]', - identifyNumber: '[ 카드 소유주 생년월일 혹은 법인 번호 ]', - extra: { - subscribeTestPayment: 1 // 100원 결제 후 결제가 되면 billing key를 발행, 결제가 실패하면 에러 - } - }) - } catch (e) { - return console.log(e) - } - console.log(response) - } -})() \ No newline at end of file diff --git a/test/subscribeCardPayment.js b/test/subscribeCardPayment.js new file mode 100644 index 0000000..e940487 --- /dev/null +++ b/test/subscribeCardPayment.js @@ -0,0 +1,20 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestSubscribeCardPayment({ + billing_key: '62579f4bcf9f6d001d0aed21', + order_name: '테스트 결제', + order_id: (new Date()).getTime(), + price: 100, + tax_free: 0 + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/subscribe_billing.js b/test/subscribe_billing.js deleted file mode 100644 index 8d961d8..0000000 --- a/test/subscribe_billing.js +++ /dev/null @@ -1,24 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.requestSubscribeBillingPayment({ - billingKey: '5f97b8a40f606f03e8ab32a0', - itemName: '테스트', - price: 1000, - orderId: (new Date()).getTime(), - feedbackUrl: 'https://dev-api.bootpay.co.kr/callback', - feedbackContentType: 'json' - }) - } catch (e) { - return console.log(e) - } - console.log(response) - } -})() \ No newline at end of file diff --git a/test/verify.js b/test/verify.js deleted file mode 100644 index cc7a53c..0000000 --- a/test/verify.js +++ /dev/null @@ -1,17 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - const token = await Bootpay.getAccessToken() - if (token.status === 200) { - let result - try { - result = await Bootpay.verify('5f0d42a7d111902931bea5ff') - } catch (e) { - return console.log(e) - } - console.log(result) - } -})() \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index bf25cbc..bdef80e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,9 @@ { "compilerOptions": { - "target": "es5", + "target": "es6", "lib": [ - "es6" + "es6", + "DOM" ], "module": "commonjs", "moduleResolution": "node", From f7d6b1c9b741c0d20740d384db816470ddbcd62c Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 14 Apr 2022 16:48:04 +0900 Subject: [PATCH 002/117] =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EB=82=A0=EC=A7=9C?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bootpay.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/bootpay.ts b/src/bootpay.ts index 225f454..a55ac4d 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -18,9 +18,7 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * Get Access Token * Comment by GOSOMI - * @date: 2022-04-12 - * @param - * @returns + * @returns Promise */ async getAccessToken(): Promise { try { @@ -40,7 +38,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * Lookup Receipt * Comment by GOSOMI - * @date: 2022-04-13 * @param receiptId: string */ async receiptPayment(receiptId: string): Promise { @@ -55,7 +52,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * Cancel Payment * Comment by GOSOMI - * @date: 2022-04-13 * @param cancelPayment: CancelPaymentParameters * @returns Promise */ @@ -73,7 +69,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * Lookup Certificate Data * Comment by GOSOMI - * @date: 2022-04-14 * @param receiptId: string * @returns Promise */ @@ -89,7 +84,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * ConfirmPayment * Comment by GOSOMI - * @date: 2022-04-14 * @param receiptId: string * @returns Promise */ @@ -107,7 +101,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * lookupSubscribeBillingKey * Comment by GOSOMI - * @date: 2022-04-14 * @param receiptId: string * @returns Promise */ @@ -123,7 +116,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * requestSubscribeBillingKey * Comment by GOSOMI - * @date: 2022-04-14 * @param subscriptionBillingRequest: SubscriptionBillingRequestParameters * @returns Promise */ @@ -141,7 +133,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * requestSubscribeCardPayment * Comment by GOSOMI - * @date: 2022-04-14 * @param subscriptionCardRequest: SubscriptionCardPaymentRequestParameters * @returns Promise */ @@ -159,7 +150,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * destroyBillingKey * Comment by GOSOMI - * @date: 2022-04-14 * @param billingKey:string * @returns Promise */ @@ -175,7 +165,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * requestUserToken * Comment by GOSOMI - * @date: 2022-04-14 * @param userTokenRequest:UserTokenRequestParameters * @returns Promise */ From ab4e352746a267aee68fe603dd7cf7d85f40651d Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 14 Apr 2022 18:01:19 +0900 Subject: [PATCH 003/117] request access token --- test/{access_token.js => requestAccessToken.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{access_token.js => requestAccessToken.js} (100%) diff --git a/test/access_token.js b/test/requestAccessToken.js similarity index 100% rename from test/access_token.js rename to test/requestAccessToken.js From df4ed44bf358830643e6662e0cdacda0490e4384 Mon Sep 17 00:00:00 2001 From: gosomi Date: Fri, 15 Apr 2022 16:03:02 +0900 Subject: [PATCH 004/117] =?UTF-8?q?method=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/response.ts b/src/lib/response.ts index d56d3ba..027b265 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -124,6 +124,7 @@ export interface AuthenticateData { export interface SubscriptionBillingRequestParameters { pg: string + method?: string order_name: string subscription_id: string card_no: string From 926a6e87d4fe366df0319e786cbcc5169f50441f Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 18 Apr 2022 09:30:38 +0900 Subject: [PATCH 005/117] =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4ffbb3..e963756 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "bootpay-backend-nodejs", + "name": "@bootpay/backend-js", "version": "2.0.0", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", From 5c799f746fdf7828e86832a3368dbd17476b1444 Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 20 Apr 2022 16:33:02 +0900 Subject: [PATCH 006/117] =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bootpay.ts | 22 +++++++++++++++++++++- src/lib/response.ts | 15 +++++++++++++++ test/subscribePaymentReserve.js | 21 +++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 test/subscribePaymentReserve.js diff --git a/src/bootpay.ts b/src/bootpay.ts index a55ac4d..884c001 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -7,7 +7,9 @@ import { ReceiptResponseParameters, SubscriptionBillingRequestParameters, SubscriptionBillingResponseParameters, - SubscriptionCardPaymentRequestParameters, UserTokenRequestParameters, UserTokenResponseParameters + SubscriptionCardPaymentRequestParameters, UserTokenRequestParameters, UserTokenResponseParameters, + SubscribePaymentReserveParameters, + SubscribePaymentReserveResponse } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -178,6 +180,24 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { return Promise.reject(e) } } + + /** + * subscribePaymentReserve + * Comment by GOSOMI + * @date: 2022-04-20 + * @param subscribePaymentReserveRequest:SubscribePaymentReserveParameters + * @returns Promise + */ + async subscribePaymentReserve(subscribePaymentReserveRequest: SubscribePaymentReserveParameters) { + try { + const response: SubscribePaymentReserveResponse = await this.post('subscribe/payment/reserve', { + ...subscribePaymentReserveRequest + }) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } } export const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() \ No newline at end of file diff --git a/src/lib/response.ts b/src/lib/response.ts index 027b265..cafc559 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -182,4 +182,19 @@ export interface UserTokenRequestParameters { export interface UserTokenResponseParameters { user_token: string expired_at: Date +} + +export interface SubscribePaymentReserveParameters { + billing_key: string + order_name: string + price: number + tax_free?: number + user?: UserModel + items?: ItemModel + reserve_execute_at: string +} + +export interface SubscribePaymentReserveResponse { + reserve_id: string + reserve_execute_at: string } \ No newline at end of file diff --git a/test/subscribePaymentReserve.js b/test/subscribePaymentReserve.js new file mode 100644 index 0000000..75e68d5 --- /dev/null +++ b/test/subscribePaymentReserve.js @@ -0,0 +1,21 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + // console.log(new Date((new Date()).getTime() + 5000)) + await Bootpay.getAccessToken() + const response = await Bootpay.subscribePaymentReserve({ + billing_key: '[ billing key ]', + order_name: '테스트 결제', + order_id: (new Date()).getTime(), + price: 1000, + reserve_execute_at: new Date((new Date()).getTime() + 5000) + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file From f80f205d57077db8e1956fbc1083b4397fec8867 Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 21 Apr 2022 14:51:28 +0900 Subject: [PATCH 007/117] =?UTF-8?q?=EC=98=88=EC=95=BD=EA=B2=B0=EC=A0=9C=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bootpay.ts | 19 +++++++++++++++++-- src/lib/response.ts | 5 +++++ test/cancelSubscribeReserve.js | 24 ++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 test/cancelSubscribeReserve.js diff --git a/src/bootpay.ts b/src/bootpay.ts index 884c001..b473b10 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -9,7 +9,8 @@ import { SubscriptionBillingResponseParameters, SubscriptionCardPaymentRequestParameters, UserTokenRequestParameters, UserTokenResponseParameters, SubscribePaymentReserveParameters, - SubscribePaymentReserveResponse + SubscribePaymentReserveResponse, + CancelSubscribeReserveResponse } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -184,7 +185,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { /** * subscribePaymentReserve * Comment by GOSOMI - * @date: 2022-04-20 * @param subscribePaymentReserveRequest:SubscribePaymentReserveParameters * @returns Promise */ @@ -198,6 +198,21 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { return Promise.reject(e) } } + + /** + * cancelSubscribeReserve + * Comment by GOSOMI + * @param reserveId:string + * @returns Promise + */ + async cancelSubscribeReserve(reserveId: string) { + try { + const response: CancelSubscribeReserveResponse = await this.delete(`subscribe/payment/reserve/${ reserveId }`) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } } export const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() \ No newline at end of file diff --git a/src/lib/response.ts b/src/lib/response.ts index cafc559..f7d7d5f 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -197,4 +197,9 @@ export interface SubscribePaymentReserveParameters { export interface SubscribePaymentReserveResponse { reserve_id: string reserve_execute_at: string +} + +export interface CancelSubscribeReserveResponse { + reserve_id: string + success: boolean } \ No newline at end of file diff --git a/test/cancelSubscribeReserve.js b/test/cancelSubscribeReserve.js new file mode 100644 index 0000000..c31cf21 --- /dev/null +++ b/test/cancelSubscribeReserve.js @@ -0,0 +1,24 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + // console.log(new Date((new Date()).getTime() + 5000)) + await Bootpay.getAccessToken() + const response = await Bootpay.subscribePaymentReserve({ + billing_key: '[ 빌링키 ]', + order_name: '테스트 결제', + order_id: (new Date()).getTime(), + price: 1000, + reserve_execute_at: new Date((new Date()).getTime() + 5000) + }) + if (response.reserve_id !== undefined) { + const cancel = await Bootpay.cancelSubscribeReserve(response.reserve_id) + console.log(cancel) + } + } catch (e) { + console.log(e) + } +})() \ No newline at end of file From b75e3efe02c1d43c580940a8a9b7c4663e8cd3ab Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 18 May 2022 13:35:18 +0900 Subject: [PATCH 008/117] =?UTF-8?q?=EB=B2=A0=ED=83=80=201=EC=B0=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8B=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 385 +-------------------------------------------------- package.json | 4 +- src/index.js | 3 + 3 files changed, 7 insertions(+), 385 deletions(-) create mode 100644 src/index.js diff --git a/README.md b/README.md index 05e25f3..d88031e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Bootpay Server Side Package for Node.js [![alt text](https://cdn.bootpay.co.kr/icon/npm.svg)](https://www.npmjs.com/package/bootpay-backend-nodejs) - ## Bootpay Node.js Server Side Library + 부트페이 공식 Node.js 라이브러리 입니다 (서버사이드 용) node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용가능합니다. @@ -9,394 +9,13 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 * PG 결제창 연동은 클라이언트 라이브러리에서 수행됩니다. (Javascript, Android, iOS, React Native, Flutter 등) * 결제 검증 및 취소, 빌링키 발급, 본인인증 등의 수행은 서버사이드에서 진행됩니다. (Java, PHP, Python, Ruby, Node.js, Go, ASP.NET 등) - -## 기능 -1. (부트페이 통신을 위한) 토큰 발급 요청 -2. 결제 검증 -3. 결제 취소 (전액 취소 / 부분 취소) -4. 빌링키 발급 - - 4-1. 발급된 빌링키로 결제 승인 요청 - - 4-2. 발급된 빌링키로 결제 승인 예약 요청 - - 4-2-1. 발급된 빌링키로 결제 승인 예약 - 취소 요청 - - 4-3. 빌링키 삭제 -5. (부트페이 단독) 사용자 토큰 발급 -6. (부트페이 단독) 결제 링크 생성 -7. 서버 승인 요청 -8. 본인 인증 결과 조회 - -## npm으로 설치하기 - -``` -npm install --save bootpay-backend-nodejs -``` - - -## 사용하기 - -```javascript -async function getAccessToken() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - try { - let response = await Bootpay.getAccessToken() - console.log(response) - } catch(e) { - console.log(e) - } -}; -``` -함수 단위의 샘플 코드는 [이곳](https://github.com/bootpay/backend-nodejs/tree/main/test)을 참조하세요. - - -## 1. 토큰 발급 - -부트페이와 서버간 통신을 하기 위해서는 부트페이 서버로부터 토큰을 발급받아야 합니다. -발급된 토큰은 30분간 유효하며, 최초 발급일로부터 30분이 지날 경우 토큰 발급 함수를 재호출 해주셔야 합니다. -```javascript -async function getAccessToken() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - try { - let response = await Bootpay.getAccessToken() - console.log(response) - } catch(e) { - console.log(e) - } -} -``` - - -## 2. 결제 검증 -결제창 및 정기결제에서 승인/취소된 결제건에 대하여 올바른 결제건인지 서버간 통신으로 결제검증을 합니다. -```javascript -async function verify() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - const token = await Bootpay.getAccessToken() - if (token.status === 200) { - let result - try { - result = await Bootpay.verify('612df0250d681b001de61de6') - } catch (e) { - return console.log(e) - } - console.log(result) - } -} -``` - - -## 3. 결제 취소 (전액 취소 / 부분 취소) -price를 지정하지 않으면 전액취소 됩니다. -* 휴대폰 결제의 경우 이월될 경우 이통사 정책상 취소되지 않습니다 -* 정산받으실 금액보다 취소금액이 클 경우 PG사 정책상 취소되지 않을 수 있습니다. 이때 PG사에 문의하시면 되겠습니다. -* 가상계좌의 경우 CMS 특약이 되어있지 않으면 취소되지 않습니다. 그러므로 결제 테스트시에는 가상계좌로 테스트 하지 않길 추천합니다. - -부분취는 카드로 결제된 건만 가능하며, 일부 PG사만 지원합니다. 요청시 price에 금액을 지정하시면 되겠습니다. -* (지원가능 PG사: 이니시스, kcp, 다날, 페이레터, 나이스페이, 카카오페이, 페이코) - -간혹 개발사에서 실수로 여러번 부분취소를 보내서 여러번 취소되는 경우가 있기때문에, 부트페이에서는 부분취소 중복 요청을 막기 위해 cancel_id 라는 필드를 추가했습니다. cancel_id를 지정하시면, 해당 건에 대해 중복 요청방지가 가능합니다. -```javascript -async function cancel() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.cancel({ - receiptId: '612df0250d681b001de61de6', - price: 1000, - name: '취소자명', - reason: '취소합니다' - }) - } catch (e) { - console.log(e) - return - } - console.log(response) - } -} -``` - -## 4. 빌링키 발급 -REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에게 빌링키를 발급받을 수 있습니다. -발급받은 빌링키를 저장하고 있다가, 원하는 시점, 원하는 금액에 결제 승인 요청하여 좀 더 자유로운 결제시나리오에 적용이 가능합니다. -* 비인증 정기결제(REST API) 방식을 지원하는 PG사만 사용 가능합니다. -```javascript -async function getBillingKey() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.requestSubscribeBillingKey({ - orderId: (new Date()).getTime(), - pg: 'nicepay', - itemName: '정기결제 30일권', - cardNo: '[ 카드 번호 ]', - cardPw: '[ 카드 비밀번호 앞 2자리 ]', - expireYear: '[ 카드 만료 연도 ]', - expireMonth: '[ 카드 만료 월 ]', - identifyNumber: '[ 카드 소유주 생년월일 혹은 법인 번호 ]', - extra: { - subscribeTestPayment: 1 // 100원 결제 후 결제가 되면 billing key를 발행, 결제가 실패하면 에러 - } - }) - } catch (e) { - console.log(e) - return - } - console.log(response) - } -} -``` - -## 4-1. 발급된 빌링키로 결제 승인 요청 -발급된 빌링키로 원하는 시점에 원하는 금액으로 결제 승인 요청을 할 수 있습니다. 잔액이 부족하거나 도난 카드 등의 특별한 건이 아니면 PG사에서 결제를 바로 승인합니다. - -```javascript -async function subscribeBilling() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.requestSubscribeBillingPayment({ - billingKey: '612deb53019943001fb52312', - itemName: '테스트', - price: 1000, - orderId: (new Date()).getTime(), - feedbackUrl: 'https://dev-api.bootpay.co.kr/callback', - feedbackContentType: 'json' - }) - } catch (e) { - return console.log(e) - } - console.log(response) - } -} -``` -## 4-2. 발급된 빌링키로 결제 예약 요청 -원하는 시점에 4-1로 결제 승인 요청을 보내도 되지만, 빌링키 발급 이후에 바로 결제 예약 할 수 있습니다. (빌링키당 최대 5건) -```javascript -async function subscribeBillingReserve() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.reserveSubscribeBilling({ - billingKey: '612deb53019943001fb52312', - itemName: '테스트', - price: 1000, - orderId: (new Date()).getTime(), - userInfo: { - username: '테스트', - phone: '01000000000' - }, - feedbackUrl: 'https://dev-api.bootpay.co.kr/callback', - feedbackContentType: 'json', - schedulerType: 'oneshot', - executeAt: ((new Date()).getTime() / 1000) + 5 - }) - } catch (e) { - return console.log(e) - } - console.log(response) - } -} -``` -## 4-2-1. 발급된 빌링키로 결제 예약 - 취소 요청 -빌링키로 예약된 결제건을 취소합니다. -```javascript -async function subscribeBillingReserveCancel() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.destroyReserveSubscribeBilling('612debc70d681b0039e6133d') - console.log(response) - } catch (e) { - return console.log(e) - } - console.log(response) - } -} -``` -## 4-3. 빌링키 삭제 -발급된 빌링키로 더 이상 사용되지 않도록, 삭제 요청합니다. -```javascript -async function deleteBillingKey() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.destroySubscribeBillingKey('612debc70d681b0039e6133d') - console.log(response) - } catch (e) { - return console.log(e) - } - console.log(response) - } -} -``` -## 5. (부트페이 단독 - 간편결제창, 생체인증 기반의 사용자를 위한) 사용자 토큰 발급 -(부트페이 단독) 부트페이에서 제공하는 간편결제창, 생체인증 기반의 결제 사용을 위해서는 개발사에서 회원 고유번호를 관리해야하며, 해당 회원에 대한 사용자 토큰을 발급합니다. -이 토큰값을 기반으로 클라이언트에서 결제요청 하시면 되겠습니다. -```javascript -async function getUserToken() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.requestUserToken({ - userId: '1234', - email: 'test@gmail.com', - name: '홍길동' - }) - console.log(response) - } catch (e) { - return console.log(e) - } - console.log(response) - } -} -``` -## 6. 결제 링크 생성 -(부트페이 단독) 요청 하시면 결제링크가 리턴되며, 해당 url을 고객에게 안내, 결제 유도하여 결제를 진행할 수 있습니다. -```javascript -async function requestPayment() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.requestPayment({ - pg: 'kcp', - method: 'card', - orderId: (new Date).getTime(), - price: 1000, - itemName: '테스트 부트페이 상품', - returnUrl: 'https://dev-api.bootpay.co.kr/callback', - extra: { - expire: 30 - } - }) - } catch (e) { - return console.log(e) - } - console.log(response) - } -} -``` - -## 7. 서버 승인 요청 -결제승인 방식은 클라이언트 승인 방식과, 서버 승인 방식으로 총 2가지가 있습니다. - -클라이언트 승인 방식은 javascript나 native 등에서 confirm 함수에서 진행하는 일반적인 방법입니다만, 경우에 따라 서버 승인 방식이 필요할 수 있습니다. - -필요한 이유 -1. 100% 안정적인 결제 후 고객 안내를 위해 - 클라이언트에서 PG결제 진행 후 승인 완료될 때 onDone이 수행되지 않아 (인터넷 환경 등), 결제 이후 고객에게 안내하지 못할 수 있습니다 -2. 단일 트랜잭션의 개념이 필요할 경우 - 재고파악이 중요한 커머스를 운영할 경우 트랜잭션 개념이 필요할 수 있겠으며, 이를 위해서는 서버 승인을 사용해야 합니다. - -```javascript -async function submit() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.verify('612df0250d681b001de61de6') - } catch (e) { - return console.log(e) - } - console.log(response) - } -} -``` - -## 8. 본인 인증 결과 조회 -다날 본인인증 후 결과값을 조회합니다. -다날 본인인증에서 통신사, 외국인여부, 전화번호 이 3가지 정보는 다날에 추가로 요청하셔야 받으실 수 있습니다. -```javascript -async function certificate() { - const Bootpay = require('bootpay-backend-nodejs').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.certificate('612df0250d681b001de61de6') - } catch (e) { - return console.log(e) - } - console.log(response) - } -} -``` - ## Example 프로젝트 [적용한 샘플 프로젝트](https://github.com/bootpay/backend-nodejs-example)을 참조해주세요 ## Documentation -[부트페이 개발매뉴얼](https://bootpay.gitbook.io/docs/)을 참조해주세요 +[부트페이 개발매뉴얼](https://docs.bootpay.co.kr/next/)을 참조해주세요 ## 기술문의 diff --git a/package.json b/package.json index e963756..34c38e4 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "@bootpay/backend-js", - "version": "2.0.0", + "version": "2.0.0-beta.1", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc --build", + "build": "rm -rf ./dist && NODE_ENV=build ./node_modules/.bin/webpack --output-path=./dist --mode=production && tsc --declaration && cp ./package.json dist/package.json && cp ./src/index.js ./dist/index.js && mv ./dist/src/* ./dist && rm -rf ./dist/src && cp ./README.md dist/", "clear": "tsc --build --clean" }, "dependencies": { diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..805c732 --- /dev/null +++ b/src/index.js @@ -0,0 +1,3 @@ +import { Bootpay } from "./bootpay"; + +export { Bootpay }; \ No newline at end of file From 3800d12ddcfdf0dd8c165e5096f8ef573f150d8a Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 18 May 2022 13:48:34 +0900 Subject: [PATCH 009/117] const import export --- package.json | 2 +- src/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 34c38e4..229fa29 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "types": "dist/bootpay.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "rm -rf ./dist && NODE_ENV=build ./node_modules/.bin/webpack --output-path=./dist --mode=production && tsc --declaration && cp ./package.json dist/package.json && cp ./src/index.js ./dist/index.js && mv ./dist/src/* ./dist && rm -rf ./dist/src && cp ./README.md dist/", + "build": "rm -rf ./dist && tsc --build && cp ./package.json dist/package.json && cp ./src/index.js ./dist/index.js && rm -rf ./dist/src && cp ./README.md dist/", "clear": "tsc --build --clean" }, "dependencies": { diff --git a/src/index.js b/src/index.js index 805c732..ebc5356 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ -import { Bootpay } from "./bootpay"; +import Bootpay from "./bootpay"; export { Bootpay }; \ No newline at end of file From 97c0f7f362885b2608fa122326fd0e13a48c94f2 Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 18 May 2022 13:48:46 +0900 Subject: [PATCH 010/117] =?UTF-8?q?beta.2=20=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 229fa29..02808a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", From ba78f03224dc64227956b045b8465cd3fb945be8 Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 18 May 2022 13:54:38 +0900 Subject: [PATCH 011/117] export default common js type --- package.json | 2 +- src/bootpay.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 02808a4..b0d77b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.0-beta.2", + "version": "2.0.0-beta.3", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", diff --git a/src/bootpay.ts b/src/bootpay.ts index b473b10..4cd1702 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -215,4 +215,6 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { } } -export const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() \ No newline at end of file +const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() + +export default Bootpay \ No newline at end of file From 73106e528e3adce221086a0bc6f4b8b4fc01b504 Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 18 May 2022 14:23:20 +0900 Subject: [PATCH 012/117] =?UTF-8?q?=EC=B5=9C=EC=A2=85=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=EB=B3=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/bootpay.ts | 2 +- src/index.js | 4 +--- test/requestAccessToken.js | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index b0d77b5..1fc9093 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.0-beta.3", + "version": "2.0.0", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", diff --git a/src/bootpay.ts b/src/bootpay.ts index 4cd1702..2506356 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -217,4 +217,4 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() -export default Bootpay \ No newline at end of file +export { Bootpay } diff --git a/src/index.js b/src/index.js index ebc5356..03845e9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1 @@ -import Bootpay from "./bootpay"; - -export { Bootpay }; \ No newline at end of file +module.exports = require('./bootpay') \ No newline at end of file diff --git a/test/requestAccessToken.js b/test/requestAccessToken.js index af55542..3f14595 100644 --- a/test/requestAccessToken.js +++ b/test/requestAccessToken.js @@ -1,5 +1,5 @@ -// import { Bootpay } from "../dist/bootpay" - +// import { Bootpay } from "../dist/index.js"; +// (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay From adce28b6bfc7383d733f55e3239d7cdcdf5171ea Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 15 Jun 2022 10:37:39 +0900 Subject: [PATCH 013/117] =?UTF-8?q?=EC=97=90=EC=8A=A4=ED=81=AC=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=B0=EC=86=A1=20=EC=83=81=ED=83=9C=20=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/bootpay.ts | 17 +++++++++++- src/lib/response.ts | 43 +++++++++++++++++++++++------- test/destroySubscribeBillingKey.js | 2 +- test/shippingStart.js | 24 +++++++++++++++++ 5 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 test/shippingStart.js diff --git a/package.json b/package.json index 1fc9093..e640755 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.0", + "version": "2.0.1", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", diff --git a/src/bootpay.ts b/src/bootpay.ts index 2506356..a862615 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -10,7 +10,8 @@ import { SubscriptionCardPaymentRequestParameters, UserTokenRequestParameters, UserTokenResponseParameters, SubscribePaymentReserveParameters, SubscribePaymentReserveResponse, - CancelSubscribeReserveResponse + CancelSubscribeReserveResponse, + ShippingRequestParameters } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -213,6 +214,20 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { return Promise.reject(e) } } + + /** + * 배송시작 REST API 시작 + * Comment by GOSOMI + * @date: 2022-06-14 + */ + async shippingStart(shippingRequest: ShippingRequestParameters): Promise { + try { + const response: ReceiptResponseParameters = await this.put(`escrow/shipping/start/${ shippingRequest.receipt_id }`, shippingRequest) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } } const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() diff --git a/src/lib/response.ts b/src/lib/response.ts index f7d7d5f..de41bd6 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -21,12 +21,12 @@ export interface ReceiptResponseParameters { purchased_at?: Date requested_at: Date cancelled_at?: Date - escrow_status?: string status: number card_data?: CardData, phone_data?: PhoneData, bank_data?: BankData vbank_data?: BankData + escrow_data?: EscrowData } export interface ExtraModel { @@ -34,17 +34,25 @@ export interface ExtraModel { } export interface UserModel { - id: string - username: string - phone: string - email: string + id?: string + username?: string + phone?: string + email?: string +} + +export interface CompanyModel { + name?: string + phone?: string + zipcode?: string + addr1?: string + addr2?: string } export interface ItemModel { - id: string - name: string - qty: number - price: number + id?: string + name?: string + qty?: number + price?: number } export interface BillingData { @@ -52,7 +60,7 @@ export interface BillingData { card_company: string card_company_code: string card_type: string - card_hash: string + card_hash?: string } export interface CardData { @@ -88,6 +96,13 @@ export interface BankData { cash_receipt_no?: string } +export interface EscrowData { + status: number + status_locale: string + shipping_started_at: Date + receipt_confirmed_at: Date | null +} + export interface CancelPaymentParameters { receipt_id: string cancel_price?: number @@ -194,6 +209,14 @@ export interface SubscribePaymentReserveParameters { reserve_execute_at: string } +export interface ShippingRequestParameters { + receipt_id: string + tracking_number: string + delivery_corp: string + user?: UserModel + company?: CompanyModel +} + export interface SubscribePaymentReserveResponse { reserve_id: string reserve_execute_at: string diff --git a/test/destroySubscribeBillingKey.js b/test/destroySubscribeBillingKey.js index 31b943d..2ab76b6 100644 --- a/test/destroySubscribeBillingKey.js +++ b/test/destroySubscribeBillingKey.js @@ -6,7 +6,7 @@ }) try { await Bootpay.getAccessToken() - const response = await Bootpay.destroyBillingKey('62579f4bcf9f6d001d0aed21') + const response = await Bootpay.destroyBillingKey('628b2579d01c7e00219b6076') console.log(response) } catch (e) { console.log(e) diff --git a/test/shippingStart.js b/test/shippingStart.js new file mode 100644 index 0000000..00799ae --- /dev/null +++ b/test/shippingStart.js @@ -0,0 +1,24 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.shippingStart({ + receipt_id: "62a9379ad01c7e001f7dc1f3", + tracking_number: '123456', + delivery_corp: 'CJ대한통운', + user: { + username: '테스트', + phone: '01000000000', + address: '서울특별시 종로구', + zipcode: '08490' + } + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file From 40e7fe0ab87af271a3ec4e6acef57c4a668028af Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 15 Jun 2022 11:25:23 +0900 Subject: [PATCH 014/117] =?UTF-8?q?shipping=20model=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/response.ts b/src/lib/response.ts index de41bd6..c2ae134 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -213,6 +213,8 @@ export interface ShippingRequestParameters { receipt_id: string tracking_number: string delivery_corp: string + shipping_prepayment?: boolean + shipping_day?: number user?: UserModel company?: CompanyModel } From 6d91a6cfbadd3aeab5d63712a8a70ea8d0b4702d Mon Sep 17 00:00:00 2001 From: gosomi Date: Fri, 17 Jun 2022 09:14:00 +0900 Subject: [PATCH 015/117] =?UTF-8?q?2.0.2=20=EB=B0=B0=ED=8F=AC=20=EC=A4=80?= =?UTF-8?q?=EB=B9=84=20method=20symbol=20typescript=20=EC=A0=95=EC=9D=98?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/lib/response.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e640755..156be0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.1", + "version": "2.0.2", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", diff --git a/src/lib/response.ts b/src/lib/response.ts index c2ae134..9c21d2e 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -17,7 +17,9 @@ export interface ReceiptResponseParameters { sandbox: boolean pg: string method: string + method_symbol: string // legacy method alias method_origin: string + method_origin_symbol: string // legacy method origin alias purchased_at?: Date requested_at: Date cancelled_at?: Date From e8600801d760d2066af259eab07163083c773805 Mon Sep 17 00:00:00 2001 From: ehowlsla Date: Thu, 23 Jun 2022 13:23:16 +0900 Subject: [PATCH 016/117] test code and readme update --- README.md | 389 +++++++++++++++++- test/cancelPayment.js | 6 +- test/cancelSubscribeReserve.js | 6 +- test/confirmPayment.js | 15 + test/destroySubscribeBillingKey.js | 6 +- ...equestAccessToken.js => getAccessToken.js} | 0 test/getBillingKey.js | 27 ++ test/lookupSubscribeBilling.js | 6 +- test/receiptPayment.js | 6 +- test/requestUserToken.js | 7 +- test/request_payment.js | 55 +-- test/subscribeCardPayment.js | 6 +- test/subscribePaymentReserve.js | 6 +- test/subscribe_billing_reserve.js | 30 -- test/subscribe_billing_reserve_cancel.js | 34 -- 15 files changed, 482 insertions(+), 117 deletions(-) create mode 100644 test/confirmPayment.js rename test/{requestAccessToken.js => getAccessToken.js} (100%) create mode 100644 test/getBillingKey.js delete mode 100644 test/subscribe_billing_reserve.js delete mode 100644 test/subscribe_billing_reserve_cancel.js diff --git a/README.md b/README.md index d88031e..b2cf1c8 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,394 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 * PG 결제창 연동은 클라이언트 라이브러리에서 수행됩니다. (Javascript, Android, iOS, React Native, Flutter 등) * 결제 검증 및 취소, 빌링키 발급, 본인인증 등의 수행은 서버사이드에서 진행됩니다. (Java, PHP, Python, Ruby, Node.js, Go, ASP.NET 등) + +## 기능 +1. (부트페이 통신을 위한) 토큰 발급 +2. 결제 단건 조회 +3. 결제 취소 (전액 취소 / 부분 취소) +4. 신용카드 자동결제 (빌링결제) + + 4-1. 빌링키 발급 + + 4-2. 발급된 빌링키로 결제 승인 요청 + + 4-3. 발급된 빌링키로 결제 예약 요청 + + 4-4. 발급된 빌링키로 결제 예약 - 취소 요청 + + 4-5. 빌링키 삭제 + + 4-6. 빌링키 조회 + +5. (생체인증, 비밀번호 결제를 위한) 구매자 토큰 발급 +6. 서버 승인 요청 +7. 본인 인증 결과 조회 +8. (에스크로 이용시) PG사로 배송정보 보내기 + + +## npm으로 설치하기 + + +``` +npm install --save @bootpay/backend-js +``` + +# 사용하기 + +```javascript +(async () => { + const Bootpay = require('@bootpay/backend-js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.cancelPayment({ + receipt_id: '628b2206d01c7e00209b6087', + cancel_price: 1000, + cancel_username: '테스트 사용자', + cancel_message: '테스트 취소입니다.' + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + + +## 1. (부트페이 통신을 위한) 토큰 발급 + +부트페이와 서버간 통신을 하기 위해서는 부트페이 서버로부터 토큰을 발급받아야 합니다. +발급된 토큰은 30분간 유효하며, 최초 발급일로부터 30분이 지날 경우 토큰 발급 함수를 재호출 해주셔야 합니다. + +```javascript +(async () => { + const Bootpay = require('@bootpay/backend-js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + let response = await Bootpay.getAccessToken() + console.log(response) + } catch (e) { + console.log(e) + } +})() + +``` + + +## 2. 결제 단건 조회 +결제창 및 정기결제에서 승인/취소된 결제건에 대하여 올바른 결제건인지 서버간 통신으로 결제검증을 합니다. +```javascript +(async () => { + const Bootpay = require('@bootpay/backend-js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.receiptPayment('62b12f4b6262500007629fec') + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + +## 3. 결제 취소 (전액 취소 / 부분 취소) +price를 지정하지 않으면 전액취소 됩니다. +* 휴대폰 결제의 경우 이월될 경우 이통사 정책상 취소되지 않습니다 +* 정산받으실 금액보다 취소금액이 클 경우 PG사 정책상 취소되지 않을 수 있습니다. 이때 PG사에 문의하시면 되겠습니다. +* 가상계좌의 경우 CMS 특약이 되어있지 않으면 취소되지 않습니다. 그러므로 결제 테스트시에는 가상계좌로 테스트 하지 않길 추천합니다. + +부분취는 카드로 결제된 건만 가능하며, 일부 PG사만 지원합니다. 요청시 price에 금액을 지정하시면 되겠습니다. +* (지원가능 PG사: 이니시스, kcp, 다날, 페이레터, 나이스페이, 카카오페이, 페이코) + +간혹 개발사에서 실수로 여러번 부분취소를 보내서 여러번 취소되는 경우가 있기때문에, 부트페이에서는 부분취소 중복 요청을 막기 위해 cancel_id 라는 필드를 추가했습니다. cancel_id를 지정하시면, 해당 건에 대해 중복 요청방지가 가능합니다. +```javascript +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.cancelPayment({ + receipt_id: '628b2206d01c7e00209b6087', + cancel_price: 1000, + cancel_username: '테스트 사용자', + cancel_message: '테스트 취소입니다.' + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + +## 4-1. 빌링키 발급 +REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에게 빌링키를 발급받을 수 있습니다. +발급받은 빌링키를 저장하고 있다가, 원하는 시점, 원하는 금액에 결제 승인 요청하여 좀 더 자유로운 결제시나리오에 적용이 가능합니다. +* 비인증 정기결제(REST API) 방식을 지원하는 PG사만 사용 가능합니다. +```javascript +(async () => { + const Bootpay = require('@bootpay/backend-js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestSubscribeBillingKey({ + pg: '나이스페이', + order_name: '테스트결제', + subscription_id: (new Date()).getTime(), + card_no: '5570********1074', //카드번호 + card_pw: '**', //카드 비밀번호 2자리 + card_identity_no: '******', //카드 소유주 생년월일 6자리 + card_expire_year: '**', //카드 유효기간 년 2자리 + card_expire_month: '**', //카드 유효기간 월 2자리 + user: { + username: '홍길동', + phone: '01012345678' + } + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + +## 4-2. 발급된 빌링키로 결제 승인 요청 +발급된 빌링키로 원하는 시점에 원하는 금액으로 결제 승인 요청을 할 수 있습니다. 잔액이 부족하거나 도난 카드 등의 특별한 건이 아니면 PG사에서 결제를 바로 승인합니다. + +```python +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestSubscribeCardPayment({ + billing_key: '62b3d166cf9f6d001bd20d59', + order_name: '테스트 결제', + order_id: (new Date()).getTime(), + price: 100, + tax_free: 0 + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` +## 4-3. 발급된 빌링키로 결제 예약 요청 +원하는 시점에 4-1로 결제 승인 요청을 보내도 되지만, 빌링키 발급 이후에 바로 결제 예약 할 수 있습니다. (빌링키당 최대 10건) +```javascript +(async () => { + const Bootpay = require('@bootpay/backend-js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + // console.log(new Date((new Date()).getTime() + 5000)) + await Bootpay.getAccessToken() + const response = await Bootpay.subscribePaymentReserve({ + billing_key: '62b3d166cf9f6d001bd20d59', + order_name: '테스트 결제', + order_id: (new Date()).getTime(), + price: 1000, + reserve_execute_at: new Date((new Date()).getTime() + 5000) + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + +## 4-4. 발급된 빌링키로 결제 예약 - 취소 요청 +빌링키로 예약된 결제건을 취소합니다. +```python +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + // console.log(new Date((new Date()).getTime() + 5000)) + await Bootpay.getAccessToken() + const response = await Bootpay.subscribePaymentReserve({ + billing_key: '62b3d166cf9f6d001bd20d59', + order_name: '테스트 결제', + order_id: (new Date()).getTime(), + price: 1000, + reserve_execute_at: new Date((new Date()).getTime() + 5000) + }) + if (response.reserve_id !== undefined) { + const cancel = await Bootpay.cancelSubscribeReserve(response.reserve_id) + console.log(cancel) + } + } catch (e) { + console.log(e) + } +})() +``` + +## 4-5. 빌링키 삭제 +발급된 빌링키로 더 이상 사용되지 않도록, 삭제 요청합니다. +```python +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.destroyBillingKey('62b3d166cf9f6d001bd20d59') + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + +## 4-6. 빌링키 조회 +(빌링키 발급 완료시 리턴받았던 receipt_id에 한정) 어떤 빌링키였는지 조회합니다. +```python +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.lookupSubscribeBillingKey('62b3cbbecf9f6d001bd20ce8') + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + + +## 5. 사용자 토큰 발급 +(부트페이 단독) 부트페이에서 제공하는 간편결제창, 생체인증 기반의 결제 사용을 위해서는 개발사에서 회원 고유번호를 관리해야하며, 해당 회원에 대한 사용자 토큰을 발급합니다. +이 토큰값을 기반으로 클라이언트에서 결제요청 하시면 되겠습니다. +```javascript +(async () => { + const Bootpay = require('@bootpay/backend-js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestUserToken({ + user_id: 'gosomi1', + phone:'01012345678' + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + +## 6. 서버 승인 요청 +결제승인 방식은 클라이언트 승인 방식과, 서버 승인 방식으로 총 2가지가 있습니다. + +클라이언트 승인 방식은 pythonscript나 native 등에서 confirm 함수에서 진행하는 일반적인 방법입니다만, 경우에 따라 서버 승인 방식이 필요할 수 있습니다. + +필요한 이유 +1. 100% 안정적인 결제 후 고객 안내를 위해 - 클라이언트에서 PG결제 진행 후 승인 완료될 때 onDone이 수행되지 않아 (인터넷 환경 등), 결제 이후 고객에게 안내하지 못할 수 있습니다 +2. 단일 트랜잭션의 개념이 필요할 경우 - 재고파악이 중요한 커머스를 운영할 경우 트랜잭션 개념이 필요할 수 있겠으며, 이를 위해서는 서버 승인을 사용해야 합니다. + +```javascript +(async () => { + const Bootpay = require('@bootpay/backend-js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.confirmPayment('62876963d01c7e00209b6028') + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + +## 7. 본인 인증 결과 조회 +다날 본인인증 후 결과값을 조회합니다. +다날 본인인증에서 통신사, 외국인여부, 전화번호 이 3가지 정보는 다날에 추가로 요청하셔야 받으실 수 있습니다. +```javascript +(async () => { + const Bootpay = require('@bootpay/backend-js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.certificate('625783a6cf9f6d001d0aed19') + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + + +8. (에스크로 이용시) PG사로 배송정보 보내기 +현금 거래에 한해 구매자의 안전거래를 보장하는 방법으로, 판매자와 구매자의 온라인 전자상거래가 원활하게 이루어질 수 있도록 중계해주는 매매보호서비스입니다. 국내법에 따라 전자상거래에서 반드시 적용이 되어 있어야합니다. PG에서도 에스크로 결제를 지원하며, 에스크로 결제 사용을 원하시면 PG사 가맹시에 에스크로결제를 미리 얘기하고나서 진행을 하시는 것이 수월합니다. + +PG사로 배송정보( 이니시스, KCP만 지원 )를 보내서 에스크로 상태를 변경하는 API 입니다. +```javascript +(async () => { + const Bootpay = require('@bootpay/backend-js').Bootpay + Bootpay.setConfiguration({ + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.shippingStart({ + receipt_id: "62a9379ad01c7e001f7dc1f3", + tracking_number: '123456', + delivery_corp: 'CJ대한통운', + user: { + username: '테스트', + phone: '01000000000', + address: '서울특별시 종로구', + zipcode: '08490' + } + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + ## Example 프로젝트 -[적용한 샘플 프로젝트](https://github.com/bootpay/backend-nodejs-example)을 참조해주세요 +[적용한 샘플 프로젝트](https://github.com/bootpay/backend-python-example)을 참조해주세요 ## Documentation @@ -24,4 +409,4 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 ## License [MIT License](https://opensource.org/licenses/MIT). - \ No newline at end of file + diff --git a/test/cancelPayment.js b/test/cancelPayment.js index a97dbeb..6558bfb 100644 --- a/test/cancelPayment.js +++ b/test/cancelPayment.js @@ -1,13 +1,13 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { await Bootpay.getAccessToken() const response = await Bootpay.cancelPayment({ - receipt_id: '625763921fc19202e4747199', + receipt_id: '628b2206d01c7e00209b6087', cancel_price: 1000, cancel_username: '테스트 사용자', cancel_message: '테스트 취소입니다.' diff --git a/test/cancelSubscribeReserve.js b/test/cancelSubscribeReserve.js index c31cf21..8a94c2f 100644 --- a/test/cancelSubscribeReserve.js +++ b/test/cancelSubscribeReserve.js @@ -1,14 +1,14 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { // console.log(new Date((new Date()).getTime() + 5000)) await Bootpay.getAccessToken() const response = await Bootpay.subscribePaymentReserve({ - billing_key: '[ 빌링키 ]', + billing_key: '62b3d166cf9f6d001bd20d59', order_name: '테스트 결제', order_id: (new Date()).getTime(), price: 1000, diff --git a/test/confirmPayment.js b/test/confirmPayment.js new file mode 100644 index 0000000..91ba15a --- /dev/null +++ b/test/confirmPayment.js @@ -0,0 +1,15 @@ +// import { Bootpay } from "../dist/bootpay" +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.confirmPayment('62876963d01c7e00209b6028') + console.log(response) + } catch (e) { + console.log(e) + } +})() diff --git a/test/destroySubscribeBillingKey.js b/test/destroySubscribeBillingKey.js index 2ab76b6..e1e58a5 100644 --- a/test/destroySubscribeBillingKey.js +++ b/test/destroySubscribeBillingKey.js @@ -1,12 +1,12 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { await Bootpay.getAccessToken() - const response = await Bootpay.destroyBillingKey('628b2579d01c7e00219b6076') + const response = await Bootpay.destroyBillingKey('62b3d166cf9f6d001bd20d59') console.log(response) } catch (e) { console.log(e) diff --git a/test/requestAccessToken.js b/test/getAccessToken.js similarity index 100% rename from test/requestAccessToken.js rename to test/getAccessToken.js diff --git a/test/getBillingKey.js b/test/getBillingKey.js new file mode 100644 index 0000000..06f2707 --- /dev/null +++ b/test/getBillingKey.js @@ -0,0 +1,27 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestSubscribeBillingKey({ + pg: '나이스페이', + order_name: '테스트결제', + subscription_id: (new Date()).getTime(), + card_no: '5570********1074', //카드번호 + card_pw: '**', //카드 비밀번호 2자리 + card_identity_no: '******', //카드 소유주 생년월일 6자리 + card_expire_year: '**', //카드 유효기간 년 2자리 + card_expire_month: '**', //카드 유효기간 월 2자리 + user: { + username: '홍길동', + phone: '01012345678' + } + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/lookupSubscribeBilling.js b/test/lookupSubscribeBilling.js index ac7a2be..9267444 100644 --- a/test/lookupSubscribeBilling.js +++ b/test/lookupSubscribeBilling.js @@ -1,12 +1,12 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { await Bootpay.getAccessToken() - const response = await Bootpay.lookupSubscribeBillingKey('6257989ecf9f6d001d0aed1b') + const response = await Bootpay.lookupSubscribeBillingKey('62b3cbbecf9f6d001bd20ce8') console.log(response) } catch (e) { console.log(e) diff --git a/test/receiptPayment.js b/test/receiptPayment.js index 86e8a71..d61d2a8 100644 --- a/test/receiptPayment.js +++ b/test/receiptPayment.js @@ -4,12 +4,12 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { await Bootpay.getAccessToken() - const response = await Bootpay.receiptPayment('61b009aaec81b4057e7f6ecd') + const response = await Bootpay.receiptPayment('62b12f4b6262500007629fec') console.log(response) } catch (e) { console.log(e) diff --git a/test/requestUserToken.js b/test/requestUserToken.js index b80c1c1..d39d33a 100644 --- a/test/requestUserToken.js +++ b/test/requestUserToken.js @@ -1,13 +1,14 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { await Bootpay.getAccessToken() const response = await Bootpay.requestUserToken({ - user_id: 'gosomi1' + user_id: 'gosomi1', + phone:'01012345678' }) console.log(response) } catch (e) { diff --git a/test/request_payment.js b/test/request_payment.js index 94e01da..8d8a738 100644 --- a/test/request_payment.js +++ b/test/request_payment.js @@ -1,27 +1,28 @@ -(async () => { - const Bootpay = require('../dist/bootpay').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - const token = await Bootpay.getAccessToken() - if (token.status === 200) { - let result - try { - result = await Bootpay.requestPayment({ - pg: 'kcp', - method: 'card', - orderId: (new Date).getTime(), - price: 1000, - itemName: '테스트 부트페이 상품', - returnUrl: 'https://dev-api.bootpay.co.kr/callback', - extra: { - expire: 30 - } - }) - } catch (e) { - return console.log(e) - } - console.log(result) - } -})() \ No newline at end of file +// @deprecated +// (async () => { +// const Bootpay = require('../dist/bootpay').Bootpay +// Bootpay.setConfig( +// '5b8f6a4d396fa665fdc2b5ea', +// 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' +// ) +// const token = await Bootpay.getAccessToken() +// if (token.status === 200) { +// let result +// try { +// result = await Bootpay.requestPayment({ +// pg: 'kcp', +// method: 'card', +// orderId: (new Date).getTime(), +// price: 1000, +// itemName: '테스트 부트페이 상품', +// returnUrl: 'https://dev-api.bootpay.co.kr/callback', +// extra: { +// expire: 30 +// } +// }) +// } catch (e) { +// return console.log(e) +// } +// console.log(result) +// } +// })() \ No newline at end of file diff --git a/test/subscribeCardPayment.js b/test/subscribeCardPayment.js index e940487..a7d1a90 100644 --- a/test/subscribeCardPayment.js +++ b/test/subscribeCardPayment.js @@ -1,13 +1,13 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribeCardPayment({ - billing_key: '62579f4bcf9f6d001d0aed21', + billing_key: '62b3d166cf9f6d001bd20d59', order_name: '테스트 결제', order_id: (new Date()).getTime(), price: 100, diff --git a/test/subscribePaymentReserve.js b/test/subscribePaymentReserve.js index 75e68d5..db51df1 100644 --- a/test/subscribePaymentReserve.js +++ b/test/subscribePaymentReserve.js @@ -1,14 +1,14 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { // console.log(new Date((new Date()).getTime() + 5000)) await Bootpay.getAccessToken() const response = await Bootpay.subscribePaymentReserve({ - billing_key: '[ billing key ]', + billing_key: '62b3d166cf9f6d001bd20d59', order_name: '테스트 결제', order_id: (new Date()).getTime(), price: 1000, diff --git a/test/subscribe_billing_reserve.js b/test/subscribe_billing_reserve.js deleted file mode 100644 index d410ce1..0000000 --- a/test/subscribe_billing_reserve.js +++ /dev/null @@ -1,30 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.reserveSubscribeBilling({ - billingKey: '5f97b8a40f606f03e8ab32a0', - itemName: '테스트', - price: 1000, - orderId: (new Date()).getTime(), - userInfo: { - username: '테스트', - phone: '01000000000' - }, - feedbackUrl: 'https://dev-api.bootpay.co.kr/callback', - feedbackContentType: 'json', - schedulerType: 'oneshot', - executeAt: ((new Date()).getTime() / 1000) + 5 - }) - } catch (e) { - return console.log(e) - } - console.log(response) - } -})() \ No newline at end of file diff --git a/test/subscribe_billing_reserve_cancel.js b/test/subscribe_billing_reserve_cancel.js deleted file mode 100644 index 177a3dc..0000000 --- a/test/subscribe_billing_reserve_cancel.js +++ /dev/null @@ -1,34 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay').Bootpay - Bootpay.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let token = await Bootpay.getAccessToken() - if (token.status === 200) { - let response - try { - response = await Bootpay.reserveSubscribeBilling({ - billingKey: '5f97b8a40f606f03e8ab32a0', - itemName: '테스트', - price: 1000, - orderId: (new Date()).getTime(), - userInfo: { - username: '테스트', - phone: '01000000000' - }, - feedbackUrl: 'https://dev-api.bootpay.co.kr/callback', - feedbackContentType: 'json', - schedulerType: 'oneshot', - executeAt: ((new Date()).getTime() / 1000) + 60 - }) - if (response.status === 200) { - response = await Bootpay.destroyReserveSubscribeBilling(response.data.reserve_id) - console.log(response) - } - } catch (e) { - return console.log(e) - } - // console.log(response) - } -})() \ No newline at end of file From 2a58eb3092079e102e1b89ee002b770a67bdce50 Mon Sep 17 00:00:00 2001 From: ehowlsla Date: Thu, 23 Jun 2022 13:37:22 +0900 Subject: [PATCH 017/117] test code and readme update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2cf1c8..093b6f0 100644 --- a/README.md +++ b/README.md @@ -275,9 +275,9 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ## 4-6. 빌링키 조회 (빌링키 발급 완료시 리턴받았던 receipt_id에 한정) 어떤 빌링키였는지 조회합니다. -```python +```javascript (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay + const Bootpay = require('@bootpay/backend-js').Bootpay Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' From d5dbfdee41ca700fc212d3b953463ac12ae5eb75 Mon Sep 17 00:00:00 2001 From: ehowlsla Date: Tue, 28 Jun 2022 10:05:53 +0900 Subject: [PATCH 018/117] readme update --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 093b6f0..f8cd534 100644 --- a/README.md +++ b/README.md @@ -177,9 +177,9 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ## 4-2. 발급된 빌링키로 결제 승인 요청 발급된 빌링키로 원하는 시점에 원하는 금액으로 결제 승인 요청을 할 수 있습니다. 잔액이 부족하거나 도난 카드 등의 특별한 건이 아니면 PG사에서 결제를 바로 승인합니다. -```python +```javascript (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay + const Bootpay = require('@bootpay/backend-js').Bootpay Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -227,9 +227,9 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ## 4-4. 발급된 빌링키로 결제 예약 - 취소 요청 빌링키로 예약된 결제건을 취소합니다. -```python +```javascript (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay + const Bootpay = require('@bootpay/backend-js').Bootpay Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -256,9 +256,9 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ## 4-5. 빌링키 삭제 발급된 빌링키로 더 이상 사용되지 않도록, 삭제 요청합니다. -```python +```javascript (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay + const Bootpay = require('@bootpay/backend-js').Bootpay Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -319,7 +319,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ## 6. 서버 승인 요청 결제승인 방식은 클라이언트 승인 방식과, 서버 승인 방식으로 총 2가지가 있습니다. -클라이언트 승인 방식은 pythonscript나 native 등에서 confirm 함수에서 진행하는 일반적인 방법입니다만, 경우에 따라 서버 승인 방식이 필요할 수 있습니다. +클라이언트 승인 방식은 웹, 앱에서 진행하는 일반적인 방법입니다만, 경우에 따라 서버 승인 방식이 필요할 수 있습니다. 필요한 이유 1. 100% 안정적인 결제 후 고객 안내를 위해 - 클라이언트에서 PG결제 진행 후 승인 완료될 때 onDone이 수행되지 않아 (인터넷 환경 등), 결제 이후 고객에게 안내하지 못할 수 있습니다 From b25b46807904beeb35eb379210e927b045981b18 Mon Sep 17 00:00:00 2001 From: ehowlsla Date: Tue, 28 Jun 2022 10:13:09 +0900 Subject: [PATCH 019/117] readme update --- README.md | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index f8cd534..b17d6c1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Bootpay Server Side Package for Node.js [![alt text](https://cdn.bootpay.co.kr/icon/npm.svg)](https://www.npmjs.com/package/bootpay-backend-nodejs) +# Bootpay Server Side Package for Node.js [![alt text](https://cdn.bootpay.co.kr/icon/npm.svg)](https://www.npmjs.com/package/@bootpay/backend-js) ## Bootpay Node.js Server Side Library @@ -44,8 +44,9 @@ npm install --save @bootpay/backend-js # 사용하기 ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +import { Bootpay } from "@bootpay/backend-js"; + +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -72,8 +73,7 @@ npm install --save @bootpay/backend-js 발급된 토큰은 30분간 유효하며, 최초 발급일로부터 30분이 지날 경우 토큰 발급 함수를 재호출 해주셔야 합니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '59b731f084382614ebf72215', private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' @@ -119,8 +119,7 @@ price를 지정하지 않으면 전액취소 됩니다. 간혹 개발사에서 실수로 여러번 부분취소를 보내서 여러번 취소되는 경우가 있기때문에, 부트페이에서는 부분취소 중복 요청을 막기 위해 cancel_id 라는 필드를 추가했습니다. cancel_id를 지정하시면, 해당 건에 대해 중복 요청방지가 가능합니다. ```javascript -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -145,8 +144,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 발급받은 빌링키를 저장하고 있다가, 원하는 시점, 원하는 금액에 결제 승인 요청하여 좀 더 자유로운 결제시나리오에 적용이 가능합니다. * 비인증 정기결제(REST API) 방식을 지원하는 PG사만 사용 가능합니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -178,8 +176,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 발급된 빌링키로 원하는 시점에 원하는 금액으로 결제 승인 요청을 할 수 있습니다. 잔액이 부족하거나 도난 카드 등의 특별한 건이 아니면 PG사에서 결제를 바로 승인합니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -202,8 +199,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ## 4-3. 발급된 빌링키로 결제 예약 요청 원하는 시점에 4-1로 결제 승인 요청을 보내도 되지만, 빌링키 발급 이후에 바로 결제 예약 할 수 있습니다. (빌링키당 최대 10건) ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -228,8 +224,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ## 4-4. 발급된 빌링키로 결제 예약 - 취소 요청 빌링키로 예약된 결제건을 취소합니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -257,8 +252,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ## 4-5. 빌링키 삭제 발급된 빌링키로 더 이상 사용되지 않도록, 삭제 요청합니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -276,8 +270,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ## 4-6. 빌링키 조회 (빌링키 발급 완료시 리턴받았던 receipt_id에 한정) 어떤 빌링키였는지 조회합니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -297,8 +290,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 (부트페이 단독) 부트페이에서 제공하는 간편결제창, 생체인증 기반의 결제 사용을 위해서는 개발사에서 회원 고유번호를 관리해야하며, 해당 회원에 대한 사용자 토큰을 발급합니다. 이 토큰값을 기반으로 클라이언트에서 결제요청 하시면 되겠습니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -326,8 +318,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 2. 단일 트랜잭션의 개념이 필요할 경우 - 재고파악이 중요한 커머스를 운영할 경우 트랜잭션 개념이 필요할 수 있겠으며, 이를 위해서는 서버 승인을 사용해야 합니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' @@ -346,8 +337,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 다날 본인인증 후 결과값을 조회합니다. 다날 본인인증에서 통신사, 외국인여부, 전화번호 이 3가지 정보는 다날에 추가로 요청하셔야 받으실 수 있습니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '59b731f084382614ebf72215', private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' @@ -368,8 +358,7 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 PG사로 배송정보( 이니시스, KCP만 지원 )를 보내서 에스크로 상태를 변경하는 API 입니다. ```javascript -(async () => { - const Bootpay = require('@bootpay/backend-js').Bootpay +(async () => { Bootpay.setConfiguration({ application_id: '59b731f084382614ebf72215', private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' From 05a3fd18b9242c8bc4e23d5f060098d8ccc9ff17 Mon Sep 17 00:00:00 2001 From: gosomi Date: Fri, 29 Jul 2022 09:37:44 +0900 Subject: [PATCH 020/117] =?UTF-8?q?=ED=98=84=EA=B8=88=EC=98=81=EC=88=98?= =?UTF-8?q?=EC=A6=9D=20=EB=B0=9C=ED=96=89=20=EC=B7=A8=EC=86=8C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bootpay.ts | 27 ++++++++++++++++++++++- src/lib/response.ts | 16 ++++++++++++++ test/cashReceiptPublishOnReceipt.js | 33 +++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 test/cashReceiptPublishOnReceipt.js diff --git a/src/bootpay.ts b/src/bootpay.ts index a862615..b3e5fcb 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -11,7 +11,7 @@ import { SubscribePaymentReserveParameters, SubscribePaymentReserveResponse, CancelSubscribeReserveResponse, - ShippingRequestParameters + ShippingRequestParameters, CashReceiptPublishOnReceiptParameters, CashReceiptCancelOnReceiptParameters } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -228,6 +228,31 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { return Promise.reject(e) } } + + /** + * 기존결제 현금영수증 발행 API + * Comment by GOSOMI + * @date: 2022-07-28 + */ + async cashReceiptPublishOnReceipt(cashReceiptPublishRequest: CashReceiptPublishOnReceiptParameters) { + try { + const response: ReceiptResponseParameters = await this.post('request/receipt/cash/publish', cashReceiptPublishRequest) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + + async cashReceiptCancelOnReceipt(cashReceiptCancelRequest: CashReceiptCancelOnReceiptParameters) { + try { + const response: null = await this.delete(`request/receipt/cash/cancel/${ cashReceiptCancelRequest.receipt_id }`, { + params: cashReceiptCancelRequest + }) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } } const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() diff --git a/src/lib/response.ts b/src/lib/response.ts index 9c21d2e..013a594 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -229,4 +229,20 @@ export interface SubscribePaymentReserveResponse { export interface CancelSubscribeReserveResponse { reserve_id: string success: boolean +} + +export interface CashReceiptPublishOnReceiptParameters { + receipt_id: string + username: string + email?: string + phone: string + identity_no: string + cash_receipt_type: '소득공제' | '지출증빙' + currency?: string +} + +export interface CashReceiptCancelOnReceiptParameters { + receipt_id: string + cancel_username?: string + cancel_message?: string } \ No newline at end of file diff --git a/test/cashReceiptPublishOnReceipt.js b/test/cashReceiptPublishOnReceipt.js new file mode 100644 index 0000000..9b8a043 --- /dev/null +++ b/test/cashReceiptPublishOnReceipt.js @@ -0,0 +1,33 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + // Bootpay.setConfiguration({ + // application_id: '5b8f6a4d396fa665fdc2b5ea', + // private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + // }) + Bootpay.setConfiguration({ + application_id: '59bfc738e13f337dbd6ca48a', + private_key: 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + mode: 'development' + }) + try { + // console.log(new Date((new Date()).getTime() + 5000)) + await Bootpay.getAccessToken() + const response = await Bootpay.cashReceiptPublishOnReceipt({ + receipt_id: "62e32b3f1fc192036e8db942", + username: '테스트', + email: 'test@bootpay.co.kr', + phone: '01000000000', + identity_no: '01000000000', + cash_receipt_type: '소득공제' + }) + console.log(response) + if (response.receipt_id !== undefined) { + const cancel = await Bootpay.cashReceiptCancelOnReceipt({ + receipt_id: "62e32b3f1fc192036e8db942", + }) + console.log(cancel) + } + } catch (e) { + console.log(e) + } +})() \ No newline at end of file From 824689d31cbbcb78c1ea4a536fab74d6794899d0 Mon Sep 17 00:00:00 2001 From: gosomi Date: Fri, 29 Jul 2022 11:26:20 +0900 Subject: [PATCH 021/117] =?UTF-8?q?sdk=20=EB=B2=84=EC=A0=84=20=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- src/lib/resource.ts | 15 +++++++++++++++ tsconfig.json | 10 +++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 156be0a..6e8caaa 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ }, "devDependencies": { "ts-node": "^10.7.0", - "typescript": "^4.6.3" + "typescript": "^4.7.4", + "@types/node": "^18.6.2" }, "repository": { "type": "git", diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 85a1f24..76eeaf8 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -1,5 +1,7 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' +const Package = require('../../package.json') + export interface BootpayRestApiErrorResponse { error_code?: number pg_error_code?: number @@ -24,6 +26,7 @@ export class BootpayBackendNodejsResource { mode: string bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints + apiVersion: string = '4.2.0' constructor() { this.mode = 'production' @@ -65,6 +68,9 @@ export class BootpayBackendNodejsResource { } config.headers['Content-Type'] = 'application/json' config.headers['Accept'] = 'application/json' + config.headers['BOOTPAY-SDK-VERSION'] = `backend-nodejs ${ Package.version }` + config.headers['BOOTPAY-API-VERSION'] = this.apiVersion + } return config }, (error) => { @@ -86,6 +92,15 @@ export class BootpayBackendNodejsResource { this.bootpayConfiguration = configuration } + /** + * SET API Version + * Comment by GOSOMI + * @date: 2022-07-29 + */ + setApiVersion(version: string) { + this.apiVersion = version + } + /** * Set Access Token * Comment by GOSOMI diff --git a/tsconfig.json b/tsconfig.json index bdef80e..1ba471a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,15 +2,16 @@ "compilerOptions": { "target": "es6", "lib": [ - "es6", + "esnext", "DOM" ], "module": "commonjs", "moduleResolution": "node", + "resolveJsonModule": true, + "esModuleInterop": true, "declaration": true, "outDir": "./dist", - "strict": true, - "baseUrl": "./src", + "baseUrl": "./src/", "paths": { "*": [ "../node_modules/*", @@ -18,6 +19,9 @@ ] } }, + "include": [ + "src/*" + ], "exclude": [ "**/*.spec.ts", "node_modules", From 2037c4f3bb5db17f3282afc0ab938bf155348da2 Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 8 Aug 2022 15:50:08 +0900 Subject: [PATCH 022/117] =?UTF-8?q?environment=20mode=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20optional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/resource.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 76eeaf8..adbe114 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -17,7 +17,7 @@ interface BootpayEntrypoints { interface BootpayConfiguration { application_id: string private_key: string - mode: 'development' | 'production' | 'stage' + mode?: 'development' | 'production' | 'stage' } export class BootpayBackendNodejsResource { @@ -68,8 +68,9 @@ export class BootpayBackendNodejsResource { } config.headers['Content-Type'] = 'application/json' config.headers['Accept'] = 'application/json' - config.headers['BOOTPAY-SDK-VERSION'] = `backend-nodejs ${ Package.version }` + config.headers['BOOTPAY-SDK-VERSION'] = Package.version config.headers['BOOTPAY-API-VERSION'] = this.apiVersion + config.headers['BOOTPAY-SDK-TYPE'] = 301 } return config From 5aa29689cb015bf43a5d00cd891cea39961a118f Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 9 Aug 2022 17:34:09 +0900 Subject: [PATCH 023/117] =?UTF-8?q?=ED=98=84=EA=B8=88=EC=98=81=EC=88=98?= =?UTF-8?q?=EC=A6=9D=20=EB=B3=84=EA=B1=B4=20=EB=B0=9C=ED=96=89/=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bootpay.ts | 38 +++++++++++++++++++++++++++++++++++- src/lib/response.ts | 19 ++++++++++++++++++ test/requestCashReceipt.js | 40 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 test/requestCashReceipt.js diff --git a/src/bootpay.ts b/src/bootpay.ts index b3e5fcb..0c43346 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -11,7 +11,8 @@ import { SubscribePaymentReserveParameters, SubscribePaymentReserveResponse, CancelSubscribeReserveResponse, - ShippingRequestParameters, CashReceiptPublishOnReceiptParameters, CashReceiptCancelOnReceiptParameters + ShippingRequestParameters, CashReceiptPublishOnReceiptParameters, CashReceiptCancelOnReceiptParameters, + RequestCashReceiptParameters, CancelCashReceiptParameters } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -243,6 +244,11 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { } } + /** + * 기존 결제 현금영수증 발행 취소 API + * Comment by GOSOMI + * @date: 2022-08-09 + */ async cashReceiptCancelOnReceipt(cashReceiptCancelRequest: CashReceiptCancelOnReceiptParameters) { try { const response: null = await this.delete(`request/receipt/cash/cancel/${ cashReceiptCancelRequest.receipt_id }`, { @@ -253,6 +259,36 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { return Promise.reject(e) } } + + /** + * 별건 현금영수증 발행하기 + * Comment by GOSOMI + * @date: 2022-08-09 + */ + async requestCashReceipt(cashReceiptRequest: RequestCashReceiptParameters) { + try { + const response: ReceiptResponseParameters = await this.post('request/cash/receipt', cashReceiptRequest) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + + /** + * 별건 현금영수증 취소하기 + * Comment by GOSOMI + * @date: 2022-08-09 + */ + async cancelCashReceipt(cancelCashReceiptRequest: CancelCashReceiptParameters) { + try { + const response: ReceiptResponseParameters = await this.delete(`request/cash/receipt/${ cancelCashReceiptRequest.receipt_id }`, { + params: cancelCashReceiptRequest + }) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } } const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() diff --git a/src/lib/response.ts b/src/lib/response.ts index 013a594..3c97a23 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -245,4 +245,23 @@ export interface CashReceiptCancelOnReceiptParameters { receipt_id: string cancel_username?: string cancel_message?: string +} + +export interface RequestCashReceiptParameters { + pg: string + price: number + tax_free?: number + order_name: string + cash_receipt_type: '소득공제' | '지출증빙' + identity_no: string + purchased_at: string + order_id: string + user?: UserModel + extra?: ExtraModel +} + +export interface CancelCashReceiptParameters { + receipt_id: string + cancel_username?: string + cancel_message?: string } \ No newline at end of file diff --git a/test/requestCashReceipt.js b/test/requestCashReceipt.js new file mode 100644 index 0000000..4e9e0b2 --- /dev/null +++ b/test/requestCashReceipt.js @@ -0,0 +1,40 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + // Bootpay.setConfiguration({ + // application_id: '5b8f6a4d396fa665fdc2b5ea', + // private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + // }) + Bootpay.setConfiguration({ + application_id: '59bfc738e13f337dbd6ca48a', + private_key: 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + mode: 'development' + }) + try { + // console.log(new Date((new Date()).getTime() + 5000)) + await Bootpay.getAccessToken() + const response = await Bootpay.requestCashReceipt({ + pg: '토스', + price: 1000, + tax_free: 0, + order_name: '테스트', + cash_receipt_type: '소득공제', + user: { + username: '부트페이', + phone: '01000000000', + email: 'bootpay@bootpay.co.kr' + }, + identity_no: '0100000000', + order_id: (new Date()).getTime(), + reserve_execute_at: new Date((new Date()).getTime()) + }) + console.log(response) + if (response.receipt_id !== undefined) { + const cancel = await Bootpay.cancelCashReceipt({ + receipt_id: response.receipt_id, + }) + console.log(cancel) + } + } catch (e) { + console.log(e) + } +})() \ No newline at end of file From 47c6de7185e75b95a8be9e83b7b75d9e6b9d243a Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 31 Aug 2022 09:31:18 +0900 Subject: [PATCH 024/117] =?UTF-8?q?changelog=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 41 ++++++-------------------------------- package.json | 2 +- test/requestCashReceipt.js | 19 +++++++++--------- 3 files changed, 16 insertions(+), 46 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 683340f..0673abb 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,36 +1,7 @@ -### 1.1.01 -package.json에 type module 삭제 +### 2.0.3 ( Stable ) +* 기존 결제 현금영수증 발행 +* 별건 현금영수증 발행 +* REST API 통신 요청시 Header에 버전 및 SDK 종류 명시 ( 부트페이 서버에서 CS용으로 수집 ) -### 1.1.0 -bootpay-backend-nodejs 로 패키지 네임 변경 -package.json에 type module 추가 - -### 1.0.9 -bootpay로 alias 변경 -주석 추가 -@bootpay/backend-nodejs 로 패키지 네임 변경 - -### 1.0.8 -readme 업데이트 -precompile 옵션 변경 - -### 1.0.7 -requestPayment params 데이터를 전달되도록 변경 - -### 1.0.6 -* axios instance로 생성, interceptor가 global 영향을 받지 않도록 수정 - -### 1.0.4 -* isBlank {} 체크 못하는 버그 수정 -* subscribe payment ( 정기결제 ) extra 추가 - -### 1.0.3 -* extra를 underscore로 보내는 로직 추가 - -### 1.0.2 -* item 정보를 underscore로 보내는 로직 추가 -* user_info 정보를 underscore로 보내는 로직 추가 - -### 1.0.0 -* typescript로 코딩이 되어있습니다 -* d.ts 파일이 첨부되어 typescript로도 코딩이 가능합니다. \ No newline at end of file +### 2.0.0 +새로운 v2 API에 맞도록 수정 \ No newline at end of file diff --git a/package.json b/package.json index 6e8caaa..840c50e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.2", + "version": "2.0.3", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", diff --git a/test/requestCashReceipt.js b/test/requestCashReceipt.js index 4e9e0b2..2246caf 100644 --- a/test/requestCashReceipt.js +++ b/test/requestCashReceipt.js @@ -1,21 +1,21 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay - // Bootpay.setConfiguration({ - // application_id: '5b8f6a4d396fa665fdc2b5ea', - // private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - // }) Bootpay.setConfiguration({ - application_id: '59bfc738e13f337dbd6ca48a', - private_key: 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', - mode: 'development' + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' }) + // Bootpay.setConfiguration({ + // application_id: '59bfc738e13f337dbd6ca48a', + // private_key: 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + // mode: 'development' + // }) try { // console.log(new Date((new Date()).getTime() + 5000)) await Bootpay.getAccessToken() const response = await Bootpay.requestCashReceipt({ - pg: '토스', + pg: '나이스페이', price: 1000, - tax_free: 0, + tax_free: 500, order_name: '테스트', cash_receipt_type: '소득공제', user: { @@ -25,7 +25,6 @@ }, identity_no: '0100000000', order_id: (new Date()).getTime(), - reserve_execute_at: new Date((new Date()).getTime()) }) console.log(response) if (response.receipt_id !== undefined) { From 754c0a327f97ac26072ea733a5464481d37c9257 Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 1 Sep 2022 10:06:55 +0900 Subject: [PATCH 025/117] =?UTF-8?q?package.json=20import=EA=B0=80=20?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/resource.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/resource.ts b/src/lib/resource.ts index adbe114..ed7bf87 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -1,7 +1,5 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' -const Package = require('../../package.json') - export interface BootpayRestApiErrorResponse { error_code?: number pg_error_code?: number @@ -26,7 +24,8 @@ export class BootpayBackendNodejsResource { mode: string bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints - apiVersion: string = '4.2.0' + apiVersion: string = '4.2.2' + sdkVersion: string = '2.0.3' constructor() { this.mode = 'production' @@ -68,7 +67,7 @@ export class BootpayBackendNodejsResource { } config.headers['Content-Type'] = 'application/json' config.headers['Accept'] = 'application/json' - config.headers['BOOTPAY-SDK-VERSION'] = Package.version + config.headers['BOOTPAY-SDK-VERSION'] = this.sdkVersion config.headers['BOOTPAY-API-VERSION'] = this.apiVersion config.headers['BOOTPAY-SDK-TYPE'] = 301 From 4d9b29c55d02ce3470e9811e99bd814f17945c26 Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 1 Sep 2022 10:07:37 +0900 Subject: [PATCH 026/117] =?UTF-8?q?2.0.4=20=EB=B0=B0=ED=8F=AC=20=EC=A4=80?= =?UTF-8?q?=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 8 +++++++- package.json | 2 +- src/lib/resource.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 0673abb..760ec08 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,7 +1,13 @@ -### 2.0.3 ( Stable ) +### 2.0.4 ( Stable ) + +* package.json import가 되지 않는 환경 예외처리 + +### 2.0.3 + * 기존 결제 현금영수증 발행 * 별건 현금영수증 발행 * REST API 통신 요청시 Header에 버전 및 SDK 종류 명시 ( 부트페이 서버에서 CS용으로 수집 ) ### 2.0.0 + 새로운 v2 API에 맞도록 수정 \ No newline at end of file diff --git a/package.json b/package.json index 840c50e..1319454 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.3", + "version": "2.0.4", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", diff --git a/src/lib/resource.ts b/src/lib/resource.ts index ed7bf87..3476a55 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -25,7 +25,7 @@ export class BootpayBackendNodejsResource { bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints apiVersion: string = '4.2.2' - sdkVersion: string = '2.0.3' + sdkVersion: string = '2.0.4' constructor() { this.mode = 'production' From 2337f3841d51a2f9b91d43fb51572ca3908cf6bf Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 5 Sep 2022 17:44:36 +0900 Subject: [PATCH 027/117] =?UTF-8?q?typescript=20import=20from=20path=20?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=EC=A0=95=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 03845e9..2ee89ee 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1,5 @@ -module.exports = require('./bootpay') \ No newline at end of file +import Bootpay from './bootpay' + +export { Bootpay } + +export default Bootpay \ No newline at end of file From 72eb0271ff8461fa4940aa89b77518023951972c Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 5 Sep 2022 18:12:33 +0900 Subject: [PATCH 028/117] =?UTF-8?q?=20typescript=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 6 +++++- package.json | 4 ++-- src/bootpay.ts | 2 ++ src/{index.js => index.ts} | 2 +- tsconfig.json | 1 + 5 files changed, 11 insertions(+), 4 deletions(-) rename src/{index.js => index.ts} (54%) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 760ec08..9a2715e 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,4 +1,8 @@ -### 2.0.4 ( Stable ) +### 2.0.5 ( Stable ) + +* typescript에서 TS7016 root에서 import가 되지 않는 문제 해결 + +### 2.0.4 * package.json import가 되지 않는 환경 예외처리 diff --git a/package.json b/package.json index 1319454..5563639 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "@bootpay/backend-js", - "version": "2.0.4", + "version": "2.0.5", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "rm -rf ./dist && tsc --build && cp ./package.json dist/package.json && cp ./src/index.js ./dist/index.js && rm -rf ./dist/src && cp ./README.md dist/", + "build": "rm -rf ./dist && tsc --build && cp ./package.json dist/package.json && rm -rf ./dist/src && cp ./README.md dist/", "clear": "tsc --build --clean" }, "dependencies": { diff --git a/src/bootpay.ts b/src/bootpay.ts index 0c43346..a6dce93 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -294,3 +294,5 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() export { Bootpay } + +export default Bootpay diff --git a/src/index.js b/src/index.ts similarity index 54% rename from src/index.js rename to src/index.ts index 2ee89ee..a00e406 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,4 +1,4 @@ -import Bootpay from './bootpay' +import { Bootpay } from './bootpay' export { Bootpay } diff --git a/tsconfig.json b/tsconfig.json index 1ba471a..81eee00 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "resolveJsonModule": true, "esModuleInterop": true, "declaration": true, + "allowJs": true, "outDir": "./dist", "baseUrl": "./src/", "paths": { From 8374cc91b3d590ac49b57f4e31a8417842a4b0a9 Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 5 Sep 2022 20:27:50 +0900 Subject: [PATCH 029/117] =?UTF-8?q?KCP=20=EC=A0=84=EC=9A=A9=20=EB=A6=AC?= =?UTF-8?q?=ED=84=B4=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/response.ts b/src/lib/response.ts index 3c97a23..f006f3f 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -63,6 +63,7 @@ export interface BillingData { card_company_code: string card_type: string card_hash?: string + rtn_key_info?: string // KCP 전용 리턴값 } export interface CardData { @@ -164,10 +165,14 @@ export interface SubscriptionBillingResponseParameters { metadata: object pg: string method: string + method_origin?: string + method_symbol?: string published_at: Date requested_at: Date - receipt_Data: ReceiptResponseParameters + receipt_data: ReceiptResponseParameters billing_expire_at: Date + status: number + status_locale?: string } export interface SubscriptionCardPaymentRequestParameters { From 2261c4ea0d38ecc10d48ae9d7f9316e16ed9a540 Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 5 Sep 2022 20:29:48 +0900 Subject: [PATCH 030/117] =?UTF-8?q?KCP=20=EC=A0=84=EC=9A=A9=20=EB=A6=AC?= =?UTF-8?q?=ED=84=B4=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/response.ts b/src/lib/response.ts index f006f3f..e11d397 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -61,7 +61,7 @@ export interface BillingData { card_no: string card_company: string card_company_code: string - card_type: string + card_type: number card_hash?: string rtn_key_info?: string // KCP 전용 리턴값 } @@ -162,6 +162,7 @@ export interface SubscriptionBillingResponseParameters { billing_data: BillingData receipt_id: string subscription_id: string + gateway_url?: string metadata: object pg: string method: string From ed8722514c884aabc350036d26414fdb785489d5 Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 5 Sep 2022 20:30:39 +0900 Subject: [PATCH 031/117] =?UTF-8?q?2.0.6=20=EB=B0=B0=ED=8F=AC=20=EC=A4=80?= =?UTF-8?q?=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 6 +++++- package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 9a2715e..6b1169e 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,4 +1,8 @@ -### 2.0.5 ( Stable ) +### 2.0.6 ( Stable ) + +* SubscriptionBillingResponseParameters interface 누락된 값 추가 ( status, status_locale, gateway_url, method_symbol ) + +### 2.0.5 * typescript에서 TS7016 root에서 import가 되지 않는 문제 해결 diff --git a/package.json b/package.json index 5563639..6535eaf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.5", + "version": "2.0.6", "description": "Bootpay Server Side Package for Node.js", "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", From 01376be2c17ef7d49b8a5f57ce719540cf69bc09 Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 5 Sep 2022 20:49:58 +0900 Subject: [PATCH 032/117] =?UTF-8?q?optional=20=EB=88=84=EB=9D=BD=EB=90=9C?= =?UTF-8?q?=20parameters=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/response.ts b/src/lib/response.ts index e11d397..225460e 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -24,6 +24,7 @@ export interface ReceiptResponseParameters { requested_at: Date cancelled_at?: Date status: number + status_locale: string card_data?: CardData, phone_data?: PhoneData, bank_data?: BankData @@ -180,7 +181,7 @@ export interface SubscriptionCardPaymentRequestParameters { billing_key: string order_name: string price: number - tax_free: number + tax_free?: number card_quota?: string card_interest?: string order_id: string From 9175b49a77fcc1269af492253564f179e6a90b21 Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 6 Sep 2022 10:16:20 +0900 Subject: [PATCH 033/117] =?UTF-8?q?main=20field=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 6535eaf..3da52f9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "@bootpay/backend-js", "version": "2.0.6", "description": "Bootpay Server Side Package for Node.js", - "main": "dist/bootpay.js", "types": "dist/bootpay.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From 08afe2e9080cc6cb4caa8f8cca7d72311b0352e1 Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 6 Sep 2022 10:21:31 +0900 Subject: [PATCH 034/117] =?UTF-8?q?interface=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=EC=9E=AC=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/response.ts b/src/lib/response.ts index 225460e..ce84155 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -25,6 +25,7 @@ export interface ReceiptResponseParameters { cancelled_at?: Date status: number status_locale: string + receipt_url?: string card_data?: CardData, phone_data?: PhoneData, bank_data?: BankData @@ -75,7 +76,7 @@ export interface CardData { card_company_code: string card_company: string card_interest: string - receipt_url: string + receipt_url?: string card_type?: string card_owner_type?: string point?: number @@ -85,6 +86,7 @@ export interface PhoneData { tid: string auth_no?: string phone?: string + receipt_url?: string } export interface BankData { @@ -98,6 +100,7 @@ export interface BankData { cash_receipt_tid?: string cash_receipt_type?: string cash_receipt_no?: string + receipt_url?: string } export interface EscrowData { From 5c61577c0ee172476625b5e33aa20168e8ac9118 Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 6 Sep 2022 10:23:30 +0900 Subject: [PATCH 035/117] =?UTF-8?q?interface=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=EC=9E=AC=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/response.ts b/src/lib/response.ts index ce84155..4d4cd31 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -154,11 +154,11 @@ export interface SubscriptionBillingRequestParameters { card_identity_no: string card_expire_year: string card_expire_month: string - price: number - tax_free: number + price?: number + tax_free?: number extra: ExtraModel user: UserModel - metadata: object + metadata?: object } export interface SubscriptionBillingResponseParameters { @@ -243,9 +243,9 @@ export interface CancelSubscribeReserveResponse { export interface CashReceiptPublishOnReceiptParameters { receipt_id: string - username: string + username?: string email?: string - phone: string + phone?: string identity_no: string cash_receipt_type: '소득공제' | '지출증빙' currency?: string From d98ac2950a564f58d24f5310856bdded925510b0 Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 6 Sep 2022 10:23:39 +0900 Subject: [PATCH 036/117] =?UTF-8?q?2.0.7=20=EB=B0=B0=ED=91=9C=20=EC=A4=80?= =?UTF-8?q?=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3da52f9..f11a8e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.6", + "version": "2.0.7", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { From 6848cf66b253f98f22f16964c56cf3678083cdc5 Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 6 Sep 2022 10:25:21 +0900 Subject: [PATCH 037/117] =?UTF-8?q?readme=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 6b1169e..f503567 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,4 +1,9 @@ -### 2.0.6 ( Stable ) +### 2.0.7 ( Stable ) + +* inteface model 정의 parameters 누락 및 optional 체크 +* 현금영수증 별건 발행 / 취소 API 추가 + +### 2.0.6 * SubscriptionBillingResponseParameters interface 누락된 값 추가 ( status, status_locale, gateway_url, method_symbol ) From 5393619c63235dadc7e5fe2d5883aee52a937d6f Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 13 Sep 2022 14:37:41 +0900 Subject: [PATCH 038/117] =?UTF-8?q?=ED=98=84=EA=B8=88=EC=98=81=EC=88=98?= =?UTF-8?q?=EC=A6=9D=20cash=5Freceipt=5Fdata=20=EC=A0=95=EC=9D=98=202.0.8?= =?UTF-8?q?=20=EB=B0=B0=ED=8F=AC=20=EC=A4=80=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 6 +++++- package.json | 2 +- src/lib/response.ts | 8 ++++++++ test/requestCashReceipt.js | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index f503567..0b7a252 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,4 +1,8 @@ -### 2.0.7 ( Stable ) +### 2.0.8 ( Stable ) + +* 현금영수증 cash_receipt_data interface 정의 + +### 2.0.7 * inteface model 정의 parameters 누락 및 optional 체크 * 현금영수증 별건 발행 / 취소 API 추가 diff --git a/package.json b/package.json index f11a8e5..749688a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.7", + "version": "2.0.8", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { diff --git a/src/lib/response.ts b/src/lib/response.ts index 4d4cd31..829d8d5 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -31,6 +31,7 @@ export interface ReceiptResponseParameters { bank_data?: BankData vbank_data?: BankData escrow_data?: EscrowData + cash_receipt_data?: CashReceiptData } export interface ExtraModel { @@ -110,6 +111,13 @@ export interface EscrowData { receipt_confirmed_at: Date | null } +export interface CashReceiptData { + tid?: string + cash_receipt_type?: number + cash_receipt_no?: string + receipt_url?: string +} + export interface CancelPaymentParameters { receipt_id: string cancel_price?: number diff --git a/test/requestCashReceipt.js b/test/requestCashReceipt.js index 2246caf..7390451 100644 --- a/test/requestCashReceipt.js +++ b/test/requestCashReceipt.js @@ -15,7 +15,7 @@ const response = await Bootpay.requestCashReceipt({ pg: '나이스페이', price: 1000, - tax_free: 500, + tax_free: 0, order_name: '테스트', cash_receipt_type: '소득공제', user: { From 3c1eabaa2661bcbd17d9898a4a43d2912135d94e Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 19 Sep 2022 08:46:20 +0900 Subject: [PATCH 039/117] =?UTF-8?q?=EC=B9=B4=EC=B9=B4=EC=98=A4=EB=A8=B8?= =?UTF-8?q?=EB=8B=88,=20=EB=84=A4=EC=9D=B4=EB=B2=84=ED=8E=98=EC=9D=B4=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8,=20=ED=86=A0=EC=8A=A4=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8,=20=ED=8E=98=EC=9D=B4=EC=BD=94=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EA=B2=B0=EC=A0=9C=EC=9D=B8=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EB=A6=AC=ED=84=B4=20=ED=8F=AC=EB=A7=B7=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib/response.ts b/src/lib/response.ts index 829d8d5..4720728 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -32,6 +32,10 @@ export interface ReceiptResponseParameters { vbank_data?: BankData escrow_data?: EscrowData cash_receipt_data?: CashReceiptData + naver_point_data?: NaverPointData + kakao_moneny_data?: KakaoMoneyData + payco_point_data?: PaycoPointData + toss_point_data?: TossPointData } export interface ExtraModel { @@ -118,6 +122,22 @@ export interface CashReceiptData { receipt_url?: string } +export interface KakaoMoneyData { + tid?: string +} + +export interface NaverPointData { + tid?: string +} + +export interface PaycoPointData { + tid?: string +} + +export interface TossPointData { + tid?: string +} + export interface CancelPaymentParameters { receipt_id: string cancel_price?: number From 01c4b44ead21bb6a4ae0a040a22da99ef54ee6af Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 19 Sep 2022 08:47:37 +0900 Subject: [PATCH 040/117] =?UTF-8?q?2.0.9=20=EB=B2=84=EC=A0=84=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=A4=80=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 8 ++++++-- package.json | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 0b7a252..266d57d 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,6 +1,10 @@ -### 2.0.8 ( Stable ) +### 2.0.9 ( Stable ) -* 현금영수증 cash_receipt_data interface 정의 +* 네이버페이 포인트, 페이코포인트, 카카오머니, 토스포인트 결제시 리턴되는 포맷 interface 추가 정의 + +### 2.0.8 + +* 현금영수증 cash_receipt_data interface 정의 ### 2.0.7 diff --git a/package.json b/package.json index 749688a..e660023 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.8", + "version": "2.0.9", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { From 6741f9b738a040b322b538f7f6a5609cdf199883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=20=ED=83=9C=EC=84=AD?= Date: Fri, 30 Sep 2022 09:57:22 +0900 Subject: [PATCH 041/117] =?UTF-8?q?*=20=EA=B2=B0=EC=A0=9C=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EC=9A=94=EC=B2=AD=EC=8B=9C=20refund=20optional=20?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 4 ++++ package.json | 2 +- src/lib/response.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 266d57d..9d87880 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,7 @@ +### 2.1.0 + +* 결제취소 요청시 refund optional 로 수정 + ### 2.0.9 ( Stable ) * 네이버페이 포인트, 페이코포인트, 카카오머니, 토스포인트 결제시 리턴되는 포맷 interface 추가 정의 diff --git a/package.json b/package.json index e660023..1a6de59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.0.9", + "version": "2.1.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { diff --git a/src/lib/response.ts b/src/lib/response.ts index 4720728..b99a656 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -145,7 +145,7 @@ export interface CancelPaymentParameters { cancel_id?: string cancel_username?: string cancel_message?: string - refund: Refund + refund?: Refund } export interface Refund { From f9259133b4456a9901765308850612346828bae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=20=ED=83=9C=EC=84=AD?= Date: Wed, 5 Oct 2022 15:58:30 +0900 Subject: [PATCH 042/117] =?UTF-8?q?*=20=EC=A0=95=EA=B8=B0=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=20=EC=98=88=EC=95=BD=EC=8B=9C=20order=5Fid=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=A0=95=EC=9D=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 9d87880..0aa0254 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,5 +1,7 @@ -### 2.1.0 +### 2.1.1 +* 정기결제 예약시 order_id 파라미터 정의 추가 +### 2.1.0 * 결제취소 요청시 refund optional 로 수정 ### 2.0.9 ( Stable ) diff --git a/package.json b/package.json index 1a6de59..6a1a7a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.0", + "version": "2.1.1", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { From 331115cf5b8913cb54f080d37ef6247ca57abbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=20=ED=83=9C=EC=84=AD?= Date: Wed, 5 Oct 2022 16:08:57 +0900 Subject: [PATCH 043/117] =?UTF-8?q?sdk=20version=20=EB=AF=B8=EB=A6=AC=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/resource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 3476a55..22678e9 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -25,7 +25,7 @@ export class BootpayBackendNodejsResource { bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints apiVersion: string = '4.2.2' - sdkVersion: string = '2.0.4' + sdkVersion: string = '2.1.2' constructor() { this.mode = 'production' From 5251f027a79556efc4a6c9e6aab9f797e474d7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=20=ED=83=9C=EC=84=AD?= Date: Wed, 5 Oct 2022 16:39:25 +0900 Subject: [PATCH 044/117] =?UTF-8?q?version=20=EC=9E=AC=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 0aa0254..0e318e1 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.1.2 +* 버전 재배포 + ### 2.1.1 * 정기결제 예약시 order_id 파라미터 정의 추가 diff --git a/package.json b/package.json index 6a1a7a0..77b805e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.1", + "version": "2.1.2", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { From a67f8aec854c9dcc06e90810152fbf965f699530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=20=ED=83=9C=EC=84=AD?= Date: Wed, 5 Oct 2022 16:41:34 +0900 Subject: [PATCH 045/117] =?UTF-8?q?version=20=EC=9E=AC=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/response.ts b/src/lib/response.ts index b99a656..aa47a53 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -244,6 +244,7 @@ export interface SubscribePaymentReserveParameters { order_name: string price: number tax_free?: number + order_id: string user?: UserModel items?: ItemModel reserve_execute_at: string From 5909c94b0a068d203baf048a99cfc9b1ef5ad0fd Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 6 Oct 2022 15:50:13 +0900 Subject: [PATCH 046/117] =?UTF-8?q?feedback=20url,=20content=5Ftype=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=EB=A1=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/response.ts b/src/lib/response.ts index aa47a53..14ab43d 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -245,9 +245,12 @@ export interface SubscribePaymentReserveParameters { price: number tax_free?: number order_id: string + reserve_execute_at: string user?: UserModel items?: ItemModel - reserve_execute_at: string + metadata?: any + feedback_url?: string + content_type?: 'application/json' | 'application/x-www-form-urlencoded' } export interface ShippingRequestParameters { From bd991676a24f26759cf3fdc27c99841f93719ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=20=ED=83=9C=EC=84=AD?= Date: Thu, 6 Oct 2022 15:52:05 +0900 Subject: [PATCH 047/117] =?UTF-8?q?*=20=EC=A0=95=EA=B8=B0=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=EC=9A=94=EC=B2=AD=EC=8B=9C=20feedback=5Furl,=20metada?= =?UTF-8?q?ta,=20content=5Ftype=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 0e318e1..e5d9447 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.1.3 +* 정기결제요청시 feedback_url, metadata, content_type 파라미터 정의 추가 + ### 2.1.2 * 버전 재배포 diff --git a/package.json b/package.json index 77b805e..6d42504 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.2", + "version": "2.1.3", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { From aa0106606320c79fa4751917abaf4c7e1703bc75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=20=ED=83=9C=EC=84=AD?= Date: Thu, 6 Oct 2022 16:13:44 +0900 Subject: [PATCH 048/117] =?UTF-8?q?*=20=EB=82=A0=EC=A7=9C=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9D=84=20string=20->=20Date=20=EB=A1=9C=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 +++ package.json | 2 +- src/lib/response.ts | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index e5d9447..6536c20 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.1.4 +* 날짜 타입을 string -> Date 로 명시적으로 수정 + ### 2.1.3 * 정기결제요청시 feedback_url, metadata, content_type 파라미터 정의 추가 diff --git a/package.json b/package.json index 6d42504..d7157a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.3", + "version": "2.1.4", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { diff --git a/src/lib/response.ts b/src/lib/response.ts index 14ab43d..c93ac1b 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -245,7 +245,7 @@ export interface SubscribePaymentReserveParameters { price: number tax_free?: number order_id: string - reserve_execute_at: string + reserve_execute_at: Date user?: UserModel items?: ItemModel metadata?: any @@ -265,7 +265,7 @@ export interface ShippingRequestParameters { export interface SubscribePaymentReserveResponse { reserve_id: string - reserve_execute_at: string + reserve_execute_at: Date } export interface CancelSubscribeReserveResponse { @@ -296,7 +296,7 @@ export interface RequestCashReceiptParameters { order_name: string cash_receipt_type: '소득공제' | '지출증빙' identity_no: string - purchased_at: string + purchased_at?: Date order_id: string user?: UserModel extra?: ExtraModel From ae3289d2feac11f9f140d4e631a73437399aa6ed Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 7 Nov 2022 13:50:47 +0900 Subject: [PATCH 049/117] =?UTF-8?q?=EB=B3=B8=EC=9D=B8=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?REST=20API=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bootpay.ts | 49 ++++++++++++++++++++++++++++++++- src/lib/response.ts | 33 ++++++++++++++++++---- test/authenticateConfirmRest.js | 17 ++++++++++++ test/authenticateRealarmRest.js | 15 ++++++++++ test/authenticateRequestRest.js | 24 ++++++++++++++++ 5 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 test/authenticateConfirmRest.js create mode 100644 test/authenticateRealarmRest.js create mode 100644 test/authenticateRequestRest.js diff --git a/src/bootpay.ts b/src/bootpay.ts index a6dce93..bc21ea1 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -12,7 +12,7 @@ import { SubscribePaymentReserveResponse, CancelSubscribeReserveResponse, ShippingRequestParameters, CashReceiptPublishOnReceiptParameters, CashReceiptCancelOnReceiptParameters, - RequestCashReceiptParameters, CancelCashReceiptParameters + RequestCashReceiptParameters, CancelCashReceiptParameters, RequestAuthenticateParameters, AuthenticateData } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -289,6 +289,53 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { return Promise.reject(e) } } + + /** + * 본인인증 REST API 요청 + * Comment by GOSOMI + * @date: 2022-11-07 + */ + async requestAuthentication(authenticateRequest: RequestAuthenticateParameters) { + try { + const response: CertificateResponseParameters = await this.post('request/authentication', authenticateRequest) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + + /** + * 본인인증 승인하기 + * Comment by GOSOMI + * @date: 2022-11-07 + */ + async confirmAuthentication(receipt_id: string, otp: null | string = null) { + try { + const response: CertificateResponseParameters = await this.post('authenticate/confirm', { + receipt_id, + otp + }) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + + /** + * 본인인증 SMS 재전송 + * Comment by GOSOMI + * @date: 2022-11-07 + */ + async realarmAuthentication(receipt_id: string) { + try { + const response: CertificateResponseParameters = await this.post('authenticate/realarm', { + receipt_id + }) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } } const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() diff --git a/src/lib/response.ts b/src/lib/response.ts index c93ac1b..f699fe8 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -157,18 +157,25 @@ export interface Refund { export interface CertificateResponseParameters { receipt_id: string authenticate_id: string + pg: string + method: string + method_origin: string + method_origin_symbol: string authenticated_at: Date + requested_at: Date status: number + status_locale: string authenticate_data: AuthenticateData } export interface AuthenticateData { phone?: string - unique: string - birth: Date - gender: number + unique?: string + birth?: Date + gender?: number foreigner?: number carrier?: string + number_of_realarms?: number tid: string } @@ -245,7 +252,7 @@ export interface SubscribePaymentReserveParameters { price: number tax_free?: number order_id: string - reserve_execute_at: Date + reserve_execute_at: Date user?: UserModel items?: ItemModel metadata?: any @@ -296,7 +303,7 @@ export interface RequestCashReceiptParameters { order_name: string cash_receipt_type: '소득공제' | '지출증빙' identity_no: string - purchased_at?: Date + purchased_at?: Date order_id: string user?: UserModel extra?: ExtraModel @@ -306,4 +313,20 @@ export interface CancelCashReceiptParameters { receipt_id: string cancel_username?: string cancel_message?: string +} + +export interface RequestAuthenticateParameters { + authentication_id: string + pg: string + method: string + username: string + identity_no: string + carrier: string + phone: string + site_url?: string + authenticate_type?: 'sms' | 'pass' + order_name: string + extra?: ExtraModel + user?: UserModel + metadata?: object } \ No newline at end of file diff --git a/test/authenticateConfirmRest.js b/test/authenticateConfirmRest.js new file mode 100644 index 0000000..ad742f7 --- /dev/null +++ b/test/authenticateConfirmRest.js @@ -0,0 +1,17 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.confirmAuthentication( + '63688775cf9f6d0023b85f2b', + '457670' + ) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/authenticateRealarmRest.js b/test/authenticateRealarmRest.js new file mode 100644 index 0000000..d3f9b33 --- /dev/null +++ b/test/authenticateRealarmRest.js @@ -0,0 +1,15 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + // console.log(new Date((new Date()).getTime() + 5000)) + await Bootpay.getAccessToken() + const response = await Bootpay.realarmAuthentication('63688e6dd01c7e00211cbd0a') + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/authenticateRequestRest.js b/test/authenticateRequestRest.js new file mode 100644 index 0000000..0d8d3af --- /dev/null +++ b/test/authenticateRequestRest.js @@ -0,0 +1,24 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestAuthentication({ + pg: '다날', + method: '본인인증', + order_name: '테스트 인증', + authentication_id: (new Date()).getTime(), + username: '이름', + identity_no: '생년월일', + phone: '전화번호', + carrier: '통신사', + authenticate_type: 'sms' + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file From 08aef1e65f4da63081eb3da29de12adf5657f1bd Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 7 Nov 2022 13:52:29 +0900 Subject: [PATCH 050/117] =?UTF-8?q?resource=20version=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/lib/resource.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d7157a7..7b2e778 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.4", + "version": "2.1.5", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 22678e9..7629409 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -24,8 +24,8 @@ export class BootpayBackendNodejsResource { mode: string bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints - apiVersion: string = '4.2.2' - sdkVersion: string = '2.1.2' + apiVersion: string = '4.2.5' + sdkVersion: string = '2.1.5' constructor() { this.mode = 'production' From a786ae69c0b67d77e14cd2b37a58026021e78d30 Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 27 Dec 2022 14:22:03 +0900 Subject: [PATCH 051/117] =?UTF-8?q?authenticate=20data=20name=20interface?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/lib/response.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b2e778..50159e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.5", + "version": "2.1.6", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { diff --git a/src/lib/response.ts b/src/lib/response.ts index f699fe8..e7d8618 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -169,6 +169,7 @@ export interface CertificateResponseParameters { } export interface AuthenticateData { + name?: string phone?: string unique?: string birth?: Date From 6983c334c733cc64ef4e37b0cecefc66d3ac80f4 Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 7 Mar 2023 17:18:51 +0900 Subject: [PATCH 052/117] =?UTF-8?q?subscribe=20lookup=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/bootpay.ts | 30 +++++++++++++++++++++++++++--- src/lib/response.ts | 20 ++++++++++++++++++++ test/cancelSubscribeReserve.js | 8 +++++--- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 50159e0..69e4936 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.6", + "version": "2.1.7", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { diff --git a/src/bootpay.ts b/src/bootpay.ts index bc21ea1..6d6bf7a 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -7,12 +7,20 @@ import { ReceiptResponseParameters, SubscriptionBillingRequestParameters, SubscriptionBillingResponseParameters, - SubscriptionCardPaymentRequestParameters, UserTokenRequestParameters, UserTokenResponseParameters, + SubscriptionCardPaymentRequestParameters, + UserTokenRequestParameters, + UserTokenResponseParameters, SubscribePaymentReserveParameters, SubscribePaymentReserveResponse, CancelSubscribeReserveResponse, - ShippingRequestParameters, CashReceiptPublishOnReceiptParameters, CashReceiptCancelOnReceiptParameters, - RequestCashReceiptParameters, CancelCashReceiptParameters, RequestAuthenticateParameters, AuthenticateData + ShippingRequestParameters, + CashReceiptPublishOnReceiptParameters, + CashReceiptCancelOnReceiptParameters, + RequestCashReceiptParameters, + CancelCashReceiptParameters, + RequestAuthenticateParameters, + AuthenticateData, + SubscribeLookupResponse } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -201,6 +209,22 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { } } + /** + * SubscribeReserve Lookup + * Comment by GOSOMI + * @date: 2023-03-07 + * @param reserveId: string + * @returns Promise + */ + async subscribeReserveLookup(reserveId: string) { + try { + const response: SubscribeLookupResponse = await this.get(`subscribe/payment/reserve/${ reserveId }`) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + /** * cancelSubscribeReserve * Comment by GOSOMI diff --git a/src/lib/response.ts b/src/lib/response.ts index e7d8618..507e96b 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -330,4 +330,24 @@ export interface RequestAuthenticateParameters { extra?: ExtraModel user?: UserModel metadata?: object +} + +export interface SubscribeLookupResponse { + reserve_id: string + receipt_id: string + order_id: string + price: number + tax_free: number + order_name: string + user: UserModel + feedback_url: string + content_type: 'application/json' | 'application/x-www-form-urlencoded', + version: number + extra: ExtraModel + reserve_requested_at: string + reserve_execute_at: string + reserve_started_at: string + reserve_finished_at: string + reserve_revoked_at: string + status: number } \ No newline at end of file diff --git a/test/cancelSubscribeReserve.js b/test/cancelSubscribeReserve.js index 8a94c2f..6c6e501 100644 --- a/test/cancelSubscribeReserve.js +++ b/test/cancelSubscribeReserve.js @@ -1,20 +1,22 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + application_id: '59b731f084382614ebf72215', + private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' }) try { // console.log(new Date((new Date()).getTime() + 5000)) await Bootpay.getAccessToken() const response = await Bootpay.subscribePaymentReserve({ - billing_key: '62b3d166cf9f6d001bd20d59', + billing_key: '6406ef293049c8001ff5afd3', order_name: '테스트 결제', order_id: (new Date()).getTime(), price: 1000, reserve_execute_at: new Date((new Date()).getTime() + 5000) }) if (response.reserve_id !== undefined) { + const lookup = await Bootpay.subscribeReserveLookup(response.reserve_id) + console.log(lookup) const cancel = await Bootpay.cancelSubscribeReserve(response.reserve_id) console.log(cancel) } From 22fc284e829254ed18d64bf5f60fbf6b6a4fe890 Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 7 Mar 2023 18:24:47 +0900 Subject: [PATCH 053/117] =?UTF-8?q?=ED=95=A8=EC=88=98=EB=AA=85=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC=EC=84=B1=EC=9E=88=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/bootpay.ts | 6 +++--- src/lib/response.ts | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 69e4936..c883484 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.7", + "version": "2.1.8", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { diff --git a/src/bootpay.ts b/src/bootpay.ts index 6d6bf7a..a68a629 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -20,7 +20,7 @@ import { CancelCashReceiptParameters, RequestAuthenticateParameters, AuthenticateData, - SubscribeLookupResponse + SubscribePaymentLookupResponse } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -216,9 +216,9 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { * @param reserveId: string * @returns Promise */ - async subscribeReserveLookup(reserveId: string) { + async subscribePaymentReserveLookup(reserveId: string) { try { - const response: SubscribeLookupResponse = await this.get(`subscribe/payment/reserve/${ reserveId }`) + const response: SubscribePaymentLookupResponse = await this.get(`subscribe/payment/reserve/${ reserveId }`) return Promise.resolve(response) } catch (e) { return Promise.reject(e) diff --git a/src/lib/response.ts b/src/lib/response.ts index 507e96b..b2693b9 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -332,15 +332,16 @@ export interface RequestAuthenticateParameters { metadata?: object } -export interface SubscribeLookupResponse { +export interface SubscribePaymentLookupResponse { reserve_id: string - receipt_id: string + receipt_id?: string order_id: string price: number tax_free: number order_name: string user: UserModel feedback_url: string + metadata?: any, content_type: 'application/json' | 'application/x-www-form-urlencoded', version: number extra: ExtraModel From ffc03b83f287e675dd417fea2870e0b8d806db50 Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 8 Mar 2023 09:15:33 +0900 Subject: [PATCH 054/117] =?UTF-8?q?package=20version=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/lib/resource.ts | 4 ++-- test/cancelSubscribeReserve.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c883484..429e480 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.8", + "version": "2.1.9", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "scripts": { diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 7629409..b911027 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -24,8 +24,8 @@ export class BootpayBackendNodejsResource { mode: string bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints - apiVersion: string = '4.2.5' - sdkVersion: string = '2.1.5' + apiVersion: string = '4.2.7' + sdkVersion: string = '2.1.9' constructor() { this.mode = 'production' diff --git a/test/cancelSubscribeReserve.js b/test/cancelSubscribeReserve.js index 6c6e501..682686e 100644 --- a/test/cancelSubscribeReserve.js +++ b/test/cancelSubscribeReserve.js @@ -15,7 +15,7 @@ reserve_execute_at: new Date((new Date()).getTime() + 5000) }) if (response.reserve_id !== undefined) { - const lookup = await Bootpay.subscribeReserveLookup(response.reserve_id) + const lookup = await Bootpay.subscribePaymentReserveLookup(response.reserve_id) console.log(lookup) const cancel = await Bootpay.cancelSubscribeReserve(response.reserve_id) console.log(cancel) From 7499570ba8cb2f9c3d70ea097f58bf30206f95f7 Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 16 Mar 2023 09:51:43 +0900 Subject: [PATCH 055/117] =?UTF-8?q?main=20=ED=8C=8C=EC=9D=BC=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 429e480..3289e5d 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "2.1.9", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", + "main": "dist/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "rm -rf ./dist && tsc --build && cp ./package.json dist/package.json && rm -rf ./dist/src && cp ./README.md dist/", From abe4a606667ed5fb03fc0fdf0ddc5150dcbf3ad5 Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 16 Mar 2023 09:51:55 +0900 Subject: [PATCH 056/117] =?UTF-8?q?2.1.10=20=EB=B2=84=EC=A0=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3289e5d..d395cd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.9", + "version": "2.1.10", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/index.js", From 3a58f933d3fc24b54e31b5f2c880f2b5888834fa Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 16 Mar 2023 09:54:30 +0900 Subject: [PATCH 057/117] =?UTF-8?q?2.1.9=20=EB=A1=9C=20=EB=8B=A4=EC=9A=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d395cd6..3289e5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.10", + "version": "2.1.9", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/index.js", From 8a7be9bc9f61bbefe7a99d64f2356c2e055e0537 Mon Sep 17 00:00:00 2001 From: gosomi Date: Tue, 7 Nov 2023 11:19:17 +0900 Subject: [PATCH 058/117] =?UTF-8?q?user=20lookup=20data=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/bootpay.ts | 7 ++++--- src/lib/resource.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3289e5d..d395cd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.9", + "version": "2.1.10", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/index.js", diff --git a/src/bootpay.ts b/src/bootpay.ts index a68a629..e7e269c 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -35,7 +35,7 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { */ async getAccessToken(): Promise { try { - const { application_id, private_key } = this.bootpayConfiguration + const { application_id, private_key } = this.bootpayConfiguration const response: AccessTokenResponseParameters = await this.post('request/token', { application_id, private_key @@ -52,10 +52,11 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { * Lookup Receipt * Comment by GOSOMI * @param receiptId: string + * @param lookupUserData: boolean */ - async receiptPayment(receiptId: string): Promise { + async receiptPayment(receiptId: string, lookupUserData: boolean = false): Promise { try { - const response: ReceiptResponseParameters = await this.get(`receipt/${ receiptId }`) + const response: ReceiptResponseParameters = await this.get(`receipt/${ receiptId }?lookup_user_data=${ lookupUserData ? 'true' : 'false' }`) return Promise.resolve(response) } catch (e) { return Promise.reject(e) diff --git a/src/lib/resource.ts b/src/lib/resource.ts index b911027..8dece2f 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -24,8 +24,8 @@ export class BootpayBackendNodejsResource { mode: string bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints - apiVersion: string = '4.2.7' - sdkVersion: string = '2.1.9' + apiVersion: string = '4.3.3' + sdkVersion: string = '2.1.10' constructor() { this.mode = 'production' From 2fe460248e7fe767248726853d1e91cef1478f6a Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 15 Nov 2023 13:05:25 +0900 Subject: [PATCH 059/117] =?UTF-8?q?2.1.9=20sdk=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/resource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 8dece2f..e759e15 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -25,7 +25,7 @@ export class BootpayBackendNodejsResource { bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints apiVersion: string = '4.3.3' - sdkVersion: string = '2.1.10' + sdkVersion: string = '2.1.9' constructor() { this.mode = 'production' From bd05e5118c0583a3502a6fc4f961029099a332f0 Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 15 Nov 2023 13:05:47 +0900 Subject: [PATCH 060/117] =?UTF-8?q?2.1.9=20=EB=B2=84=EC=A0=84=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d395cd6..3289e5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.10", + "version": "2.1.9", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/index.js", From 6d4d89874fe68a7eaf349b1f2e0e4c77a7882711 Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 18 Dec 2023 14:03:29 +0900 Subject: [PATCH 061/117] =?UTF-8?q?2.2.0=20=EB=B2=A0=ED=83=80=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EB=B0=B0=ED=8F=AC=20=EC=A4=80=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 17 +++++++--- src/lib/resource.ts | 34 ++++++++++--------- ...irmRest.js => authenticateConfirmRest.mjs} | 9 +++-- .../{receiptPayment.js => receiptPayment.mjs} | 6 ++-- tsconfig.json | 11 ++++-- 5 files changed, 48 insertions(+), 29 deletions(-) rename test/{authenticateConfirmRest.js => authenticateConfirmRest.mjs} (55%) rename test/{receiptPayment.js => receiptPayment.mjs} (64%) diff --git a/package.json b/package.json index 3289e5d..e42e2f4 100644 --- a/package.json +++ b/package.json @@ -3,18 +3,27 @@ "version": "2.1.9", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", - "main": "dist/index.js", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/lib/request.d.ts" + } + }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "rm -rf ./dist && tsc --build && cp ./package.json dist/package.json && rm -rf ./dist/src && cp ./README.md dist/", + "build": "rm -rf ./dist && tsc --p ./tsconfig.json && cp ./package.json dist/package.json && rm -rf ./dist/src && cp ./README.md dist/", "clear": "tsc --build --clean" }, "dependencies": { - "axios": "^0.26.1" + "axios": "^1.6.2", + "@types/axios": "^0.14.0" }, "devDependencies": { "ts-node": "^10.7.0", - "typescript": "^4.7.4", + "typescript": "^5.3.3", "@types/node": "^18.6.2" }, "repository": { diff --git a/src/lib/resource.ts b/src/lib/resource.ts index e759e15..6b75f4a 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -1,3 +1,4 @@ +// @ts-expect-error import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' export interface BootpayRestApiErrorResponse { @@ -21,27 +22,27 @@ interface BootpayConfiguration { export class BootpayBackendNodejsResource { $http: AxiosInstance $token?: string - mode: string + mode: 'development' | 'production' | 'stage' bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints - apiVersion: string = '4.3.3' - sdkVersion: string = '2.1.9' + apiVersion: string = '4.3.4' + sdkVersion: string = '2.2.10' constructor() { - this.mode = 'production' - this.$http = axios.create({ + this.mode = 'production' + this.$http = axios.create({ timeout: 60000 }) - this.$token = undefined + this.$token = undefined this.bootpayConfiguration = { application_id: '', - private_key: '', - mode: 'production' + private_key: '', + mode: 'production' } - this.API_ENTRYPOINTS = { + this.API_ENTRYPOINTS = { development: 'https://dev-api.bootpay.co.kr/v2', - stage: 'https://stage-api.bootpay.co.kr/v2', - production: 'https://api.bootpay.co.kr/v2' + stage: 'https://stage-api.bootpay.co.kr/v2', + production: 'https://api.bootpay.co.kr/v2' } this.$http.interceptors.response.use((response: AxiosResponse): any => { if (response.request !== undefined && response.headers !== undefined) { @@ -56,20 +57,21 @@ export class BootpayBackendNodejsResource { } else { return Promise.reject({ error_code: -100, - message: `Request Rest Api Failed to Bootpay Server, ${ error.message }` + message: `Request Rest Api Failed to Bootpay Server, ${ error.message }` }) as BootpayRestApiErrorResponse } }) + // @ts-expect-error this.$http.interceptors.request.use((config: AxiosRequestConfig) => { if (config.headers !== undefined) { if (this.$token !== undefined) { config.headers.authorization = `Bearer ${ this.$token }` } - config.headers['Content-Type'] = 'application/json' - config.headers['Accept'] = 'application/json' + config.headers['Content-Type'] = 'application/json' + config.headers['Accept'] = 'application/json' config.headers['BOOTPAY-SDK-VERSION'] = this.sdkVersion config.headers['BOOTPAY-API-VERSION'] = this.apiVersion - config.headers['BOOTPAY-SDK-TYPE'] = 301 + config.headers['BOOTPAY-SDK-TYPE'] = 301 } return config @@ -111,7 +113,7 @@ export class BootpayBackendNodejsResource { } entrypoints(url: string): string { - return [this.API_ENTRYPOINTS[this.bootpayConfiguration.mode], url].join('/') + return [this.API_ENTRYPOINTS[this.bootpayConfiguration.mode === undefined ? 'production' : this.bootpayConfiguration.mode], url].join('/') } async get(url: string, config?: AxiosRequestConfig): Promise { diff --git a/test/authenticateConfirmRest.js b/test/authenticateConfirmRest.mjs similarity index 55% rename from test/authenticateConfirmRest.js rename to test/authenticateConfirmRest.mjs index ad742f7..4d3ea63 100644 --- a/test/authenticateConfirmRest.js +++ b/test/authenticateConfirmRest.mjs @@ -1,11 +1,14 @@ +import { Bootpay } from "../dist/bootpay.js" + (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay + // const Bootpay = require('../esm/dist/bootpay.js').Bootpay Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { - await Bootpay.getAccessToken() + const token = await Bootpay.getAccessToken() + console.log(token) const response = await Bootpay.confirmAuthentication( '63688775cf9f6d0023b85f2b', '457670' diff --git a/test/receiptPayment.js b/test/receiptPayment.mjs similarity index 64% rename from test/receiptPayment.js rename to test/receiptPayment.mjs index d61d2a8..7972818 100644 --- a/test/receiptPayment.js +++ b/test/receiptPayment.mjs @@ -1,11 +1,11 @@ -// import { Bootpay } from "../dist/bootpay" +import { Bootpay } from "../dist/bootpay.js" (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay + // const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { await Bootpay.getAccessToken() diff --git a/tsconfig.json b/tsconfig.json index 81eee00..0003482 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,17 @@ { "compilerOptions": { - "target": "es6", + "target": "esnext", + "module": "node16", + "declarationMap": false, + "sourceMap": false, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, "lib": [ "esnext", "DOM" ], - "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "node16", "resolveJsonModule": true, "esModuleInterop": true, "declaration": true, From 731a0e64ed2dc1f08b48b4105e035ecab6c278f0 Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 18 Dec 2023 14:03:47 +0900 Subject: [PATCH 062/117] =?UTF-8?q?2.2.0=20package.json=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e42e2f4..f0e4c40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.9", + "version": "2.2.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "./dist/index.js", From a4f460373bf06060139b3f28aee62ae467357817 Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 18 Dec 2023 14:53:39 +0900 Subject: [PATCH 063/117] =?UTF-8?q?module=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?index.js=20import=20=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=A7=80?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 16 +++++++++------- src/bootpay.ts | 1 - 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index f0e4c40..37dec44 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,13 @@ "version": "2.2.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", - "main": "./dist/index.js", - "module": "./dist/index.mjs", + "main": "./index.js", + "module": "./index.js", "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.js", - "types": "./dist/lib/request.d.ts" + "import": "./index.js", + "require": "./index.js", + "types": "./lib/request.d.ts" } }, "scripts": { @@ -18,8 +18,7 @@ "clear": "tsc --build --clean" }, "dependencies": { - "axios": "^1.6.2", - "@types/axios": "^0.14.0" + "axios": "^1.6.2" }, "devDependencies": { "ts-node": "^10.7.0", @@ -39,6 +38,9 @@ "부트페이", "bootpay" ], + "ts-node": { + "esm": true + }, "author": "Bootpay", "license": "MIT", "bugs": { diff --git a/src/bootpay.ts b/src/bootpay.ts index e7e269c..4691b0d 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -19,7 +19,6 @@ import { RequestCashReceiptParameters, CancelCashReceiptParameters, RequestAuthenticateParameters, - AuthenticateData, SubscribePaymentLookupResponse } from './lib/response' From e6654fc93a77a9e27d4b3e17a249fc7918c66c61 Mon Sep 17 00:00:00 2001 From: gosomi Date: Mon, 18 Dec 2023 18:07:59 +0900 Subject: [PATCH 064/117] =?UTF-8?q?beta=201=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .npmignore | 1 + package.json | 23 +++++++++++++---------- src/bootpay.ts | 3 +++ src/index.ts | 5 ----- test/receiptPayment.mjs | 3 +-- tsconfig.json | 3 +++ 6 files changed, 21 insertions(+), 17 deletions(-) delete mode 100644 src/index.ts diff --git a/.npmignore b/.npmignore index 82f4082..630bbe3 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,5 @@ src/* +test/* .gitattributes .gitignore package-lock.json diff --git a/package.json b/package.json index 37dec44..4c13a8d 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,20 @@ { - "name": "@bootpay/backend-js", - "version": "2.2.0", + "name": "@bootpay/backend", + "version": "2.2.0-beta.1", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", - "main": "./index.js", - "module": "./index.js", + "main": "dist/bootpay.js", + "module": "dist/bootpay.js", "exports": { ".": { - "import": "./index.js", - "require": "./index.js", - "types": "./lib/request.d.ts" + "import": "./dist/bootpay.js", + "require": "./dist/bootpay.js", + "types": "./dist/bootpay.d.ts" } }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "rm -rf ./dist && tsc --p ./tsconfig.json && cp ./package.json dist/package.json && rm -rf ./dist/src && cp ./README.md dist/", + "build": "rm -rf ./dist && tsc --p ./tsconfig.json && rm -rf ./dist/src && cp ./README.md dist/", "clear": "tsc --build --clean" }, "dependencies": { @@ -27,7 +27,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/bootpay/backend-nodejs" + "url": "git+https://github.com/bootpay/backend-nodejs.git" }, "keywords": [ "결제", @@ -46,5 +46,8 @@ "bugs": { "url": "https://bootpay.channel.io/" }, - "homepage": "https://www.bootpay.co.kr" + "homepage": "https://www.bootpay.co.kr", + "directories": { + "test": "test" + } } diff --git a/src/bootpay.ts b/src/bootpay.ts index 4691b0d..272c0fd 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -367,3 +367,6 @@ const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() export { Bootpay } export default Bootpay + +export * from './lib/response' +export * from './lib/resource' diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index a00e406..0000000 --- a/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Bootpay } from './bootpay' - -export { Bootpay } - -export default Bootpay \ No newline at end of file diff --git a/test/receiptPayment.mjs b/test/receiptPayment.mjs index 7972818..9892bc2 100644 --- a/test/receiptPayment.mjs +++ b/test/receiptPayment.mjs @@ -1,5 +1,4 @@ -import { Bootpay } from "../dist/bootpay.js" - +import { Bootpay } from "@bootpay/backend-js" (async () => { // const Bootpay = require('../dist/bootpay.js').Bootpay diff --git a/tsconfig.json b/tsconfig.json index 0003482..236f23f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,9 @@ "esnext", "DOM" ], + "typeRoots": [ + "./src/lib" + ], "moduleResolution": "node16", "resolveJsonModule": true, "esModuleInterop": true, From 60d5900b6f4805151b40229dff32628e386e207f Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 28 Feb 2024 12:06:45 +0900 Subject: [PATCH 065/117] =?UTF-8?q?2.2.0=20=EB=B2=84=EC=A0=84=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=A4=80=EB=B9=84=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c13a8d..1e838d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend", - "version": "2.2.0-beta.1", + "version": "2.2.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", From 7b0c452816eec13d510efb7e920b610de1f85635 Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 28 Feb 2024 12:08:23 +0900 Subject: [PATCH 066/117] =?UTF-8?q?2.1.10=20=EB=B2=84=EC=A0=84=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=A4=80=EB=B9=84=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e838d2..e944c07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend", - "version": "2.2.0", + "version": "2.1.10", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", From cada5b5da768e917f3d9efb34e2bf154a4e0f94d Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 28 Feb 2024 12:30:13 +0900 Subject: [PATCH 067/117] =?UTF-8?q?2.1.10=20=EC=A4=91=EA=B0=84=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EB=B0=B0=ED=8F=AC=20=EC=A4=80=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 ++-- src/lib/resource.ts | 1 - test/{receiptPayment.mjs => receiptPayment.js} | 2 +- tsconfig.json | 6 +++--- 4 files changed, 6 insertions(+), 7 deletions(-) rename test/{receiptPayment.mjs => receiptPayment.js} (90%) diff --git a/package.json b/package.json index e944c07..de0c558 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@bootpay/backend", - "version": "2.1.10", + "name": "@bootpay/backend-js", + "version": "2.1.10-beta3", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 6b75f4a..7edf74d 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -1,4 +1,3 @@ -// @ts-expect-error import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' export interface BootpayRestApiErrorResponse { diff --git a/test/receiptPayment.mjs b/test/receiptPayment.js similarity index 90% rename from test/receiptPayment.mjs rename to test/receiptPayment.js index 9892bc2..972c05c 100644 --- a/test/receiptPayment.mjs +++ b/test/receiptPayment.js @@ -1,4 +1,4 @@ -import { Bootpay } from "@bootpay/backend-js" +import { Bootpay } from "./" (async () => { // const Bootpay = require('../dist/bootpay.js').Bootpay diff --git a/tsconfig.json b/tsconfig.json index 236f23f..1a28210 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { - "target": "esnext", - "module": "node16", + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", "declarationMap": false, "sourceMap": false, "forceConsistentCasingInFileNames": true, @@ -14,7 +15,6 @@ "typeRoots": [ "./src/lib" ], - "moduleResolution": "node16", "resolveJsonModule": true, "esModuleInterop": true, "declaration": true, From 976fabf9571fd98992b677f63c6797186cdc1af0 Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 28 Feb 2024 12:30:22 +0900 Subject: [PATCH 068/117] =?UTF-8?q?2.1.10=20=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index de0c558..9dd2757 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.10-beta3", + "version": "2.1.10", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", From 4921a7c885308dbac803856786cdb0d0075577a1 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Mon, 11 Mar 2024 16:04:54 +0900 Subject: [PATCH 069/117] =?UTF-8?q?*=20=ED=95=84=EB=93=9C=EB=AA=85=20back?= =?UTF-8?q?=5Fusername=20->=20bank=5Fusername=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 +++ package.json | 8 ++++---- src/lib/resource.ts | 4 ++-- src/lib/response.ts | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 6536c20..604bf68 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.1.11 +* 필드명 back_username -> bank_username 으로 오타 수정 + ### 2.1.4 * 날짜 타입을 string -> Date 로 명시적으로 수정 diff --git a/package.json b/package.json index 9dd2757..5b90552 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.10", + "version": "2.1.11", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", @@ -18,12 +18,12 @@ "clear": "tsc --build --clean" }, "dependencies": { - "axios": "^1.6.2" + "axios": "^1.6.7" }, "devDependencies": { + "@types/node": "^18.6.2", "ts-node": "^10.7.0", - "typescript": "^5.3.3", - "@types/node": "^18.6.2" + "typescript": "^5.3.3" }, "repository": { "type": "git", diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 7edf74d..57d288a 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -50,7 +50,7 @@ export class BootpayBackendNodejsResource { // 오류를 리턴 return response.data as BootpayRestApiErrorResponse } - }, (error) => { + }, (error: any) => { if (error.response !== undefined) { return Promise.reject(error.response.data as BootpayRestApiErrorResponse) } else { @@ -74,7 +74,7 @@ export class BootpayBackendNodejsResource { } return config - }, (error) => { + }, (error: any) => { return Promise.reject(error) }) } diff --git a/src/lib/response.ts b/src/lib/response.ts index b2693b9..328e708 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -98,7 +98,7 @@ export interface BankData { tid: string bank_code: string bank_name: string - back_username: string + bank_username: string bank_account?: string sender_name?: string expired_at?: Date @@ -150,7 +150,7 @@ export interface CancelPaymentParameters { export interface Refund { bank_account: string - back_username: string + bank_username: string bank_code: string } From ac9dd57bc8fe09d516c2e12895530d643020e41a Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Tue, 28 May 2024 07:47:10 +0900 Subject: [PATCH 070/117] =?UTF-8?q?=EA=B3=84=EC=A2=8C=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EA=B2=B0=EC=A0=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 5 +- src/bootpay.ts | 47 +++++++++++- src/lib/resource.ts | 4 +- src/lib/response.ts | 24 +++++++ test/lookupBilling.js | 34 +++++++++ test/publishAutomaticTransferBillingKey.js | 72 +++++++++++++++++++ ...estSubscribeAutomaticTransferBillingKey.js | 55 ++++++++++++++ 7 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 test/lookupBilling.js create mode 100644 test/publishAutomaticTransferBillingKey.js create mode 100644 test/requestSubscribeAutomaticTransferBillingKey.js diff --git a/package.json b/package.json index 5b90552..88f577e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.1.11", + "version": "2.3.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", @@ -18,9 +18,10 @@ "clear": "tsc --build --clean" }, "dependencies": { - "axios": "^1.6.7" + "axios": "^1.7.2" }, "devDependencies": { + "@types/axios": "^0.14.0", "@types/node": "^18.6.2", "ts-node": "^10.7.0", "typescript": "^5.3.3" diff --git a/src/bootpay.ts b/src/bootpay.ts index 272c0fd..5c07096 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -19,7 +19,7 @@ import { RequestCashReceiptParameters, CancelCashReceiptParameters, RequestAuthenticateParameters, - SubscribePaymentLookupResponse + SubscribePaymentLookupResponse, SubscriptionBillingTransferRequestParameters } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -126,6 +126,21 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { } } + /** + * lookupBillingKey + * Comment by ehowlsla + * @param billingKey: string + * @returns Promise + */ + async lookupBillingKey(billingKey: string): Promise { + try { + const response: SubscriptionBillingResponseParameters = await this.get(`billing_key/${ billingKey }`) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + /** * requestSubscribeBillingKey * Comment by GOSOMI @@ -360,6 +375,36 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { return Promise.reject(e) } } + + /** + * 계좌 자동이체를 위한 빌링키 발급 요청 + * Comment by ehowlsla + * @date: 2024-05-27 + */ + async requestSubscribeAutomaticTransferBillingKey(parameters: SubscriptionBillingTransferRequestParameters) { + try { + const response: ReceiptResponseParameters = await this.post('request/subscribe/automatic-transfer', parameters) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + + /** + * 계좌 자동이체를 위한 출금 동의 확인 요청 + * Comment by ehowlsla + * @date: 2024-05-27 + */ + async publishAutomaticTransferBillingKey(receipt_id: string) { + try { + const response: SubscriptionBillingResponseParameters = await this.post('request/subscribe/automatic-transfer/publish', { + "receipt_id": receipt_id + }) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } } const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 57d288a..bb7db7b 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -24,8 +24,8 @@ export class BootpayBackendNodejsResource { mode: 'development' | 'production' | 'stage' bootpayConfiguration: BootpayConfiguration API_ENTRYPOINTS: BootpayEntrypoints - apiVersion: string = '4.3.4' - sdkVersion: string = '2.2.10' + apiVersion: string = '5.0.0' + sdkVersion: string = '2.3.0' constructor() { this.mode = 'production' diff --git a/src/lib/response.ts b/src/lib/response.ts index 328e708..767d991 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -36,6 +36,7 @@ export interface ReceiptResponseParameters { kakao_moneny_data?: KakaoMoneyData payco_point_data?: PaycoPointData toss_point_data?: TossPointData + currency?: string } export interface ExtraModel { @@ -197,6 +198,28 @@ export interface SubscriptionBillingRequestParameters { metadata?: object } +export interface SubscriptionBillingTransferRequestParameters { + pg: string + method?: string + order_name: string + subscription_id: string + + auth_type: 'ARS' | '간편인증' + username: string + bank_name: string + bank_account: string + identity_no: string + cash_receipt_type?: '소득공제' | '지출증빙' + cash_receipt_identity_no?: string + phone?: string + + price?: number + tax_free?: number + extra: ExtraModel + user: UserModel + metadata?: object +} + export interface SubscriptionBillingResponseParameters { billing_key: string billing_data: BillingData @@ -207,6 +230,7 @@ export interface SubscriptionBillingResponseParameters { pg: string method: string method_origin?: string + method_origin_symbol?: string method_symbol?: string published_at: Date requested_at: Date diff --git a/test/lookupBilling.js b/test/lookupBilling.js new file mode 100644 index 0000000..f0d46d8 --- /dev/null +++ b/test/lookupBilling.js @@ -0,0 +1,34 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.lookupBillingKey('66542dfb4d18d5fc7b43e1b6') + console.log(response) + } catch (e) { + console.log(e) + } +})() + +/* +{ + billing_key: '66542dfb4d18d5fc7b43e1b6', + pg: '나이스페이먼츠', + method: '계좌자동이체', + method_symbol: 'automatic_transfer_rest', + billing_data: { + bank_name: '국민', + bank_code: '004', + bank_account: '0000000000000000', + username: '윤태*' + }, + version: 2, + sandbox: 1, + expire_at: '2099-12-31T23:59:59+09:00', + published_at: '2024-05-27T15:53:47+09:00', + status: 1 +} + */ \ No newline at end of file diff --git a/test/publishAutomaticTransferBillingKey.js b/test/publishAutomaticTransferBillingKey.js new file mode 100644 index 0000000..de4ab18 --- /dev/null +++ b/test/publishAutomaticTransferBillingKey.js @@ -0,0 +1,72 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.publishAutomaticTransferBillingKey('6655069ca691573f1bb9c28a') + console.log(response) + } catch (e) { + console.log(e) + } +})() + +/* +{ + receipt_id: '6655069ca691573f1bb9c28a', + subscription_id: '1716848284697', + gateway_url: 'https://gw.bootpay.co.kr', + metadata: {}, + pg: '나이스페이먼츠', + method: '계좌자동이체', + method_symbol: 'automatic_transfer_rest', + method_origin: '계좌자동이체', + method_origin_symbol: 'automatic_transfer_rest', + published_at: '2024-05-28T07:35:43+09:00', + requested_at: '2024-05-28T07:18:04+09:00', + status_locale: '빌링키발급완료', + status: 11, + receipt_data: { + receipt_id: '66550abf3324a61b141f9205', + order_id: '1716848284697', + price: 1000, + tax_free: 0, + cancelled_price: 0, + cancelled_tax_free: 0, + order_name: '테스트결제', + company_name: '윤태섭', + gateway_url: 'https://gw.bootpay.co.kr', + metadata: {}, + sandbox: true, + pg: '나이스페이먼츠', + method: '계좌이체', + method_symbol: 'bank', + method_origin: '계좌자동이체', + method_origin_symbol: 'automatic_transfer_rest', + purchased_at: '2024-05-28T07:35:43+09:00', + requested_at: '2024-05-28T07:18:04+09:00', + status_locale: '결제완료', + currency: 'KRW', + receipt_url: 'https://door.bootpay.co.kr/receipt/WjdGUkxNWE5KTngyWlJVdFFodzM1VEdiUGtsNzBzUlJWalU9LS02dDFRVjRl%0ARFdFS0I2T1cwLS1TTjNsUHFMenlTcTFwOUVCdVZGcTBnPT0%3D%0A', + status: 1, + bank_data: { + tid: '6073543098', + bank_code: '004', + bank_name: '국민', + bank_account: '0000000000000000', + bank_username: '윤태*' + } + }, + billing_key: '66550abf3324a61b141f9206', + billing_data: { + bank_name: '국민', + bank_code: '004', + bank_account: '0000000000000000', + username: '윤태*' + }, + billing_expire_at: '2099-12-31T23:59:59+09:00' +} + + */ diff --git a/test/requestSubscribeAutomaticTransferBillingKey.js b/test/requestSubscribeAutomaticTransferBillingKey.js new file mode 100644 index 0000000..05a9579 --- /dev/null +++ b/test/requestSubscribeAutomaticTransferBillingKey.js @@ -0,0 +1,55 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestSubscribeAutomaticTransferBillingKey({ + pg: '나이스페이', + order_name: '테스트결제', + subscription_id: (new Date()).getTime(), + price: 1000, + username: '윤태섭', + bank_name: '국민', + bank_account: '67560101092472', + identity_no: '861014', + cash_receipt_identity_no: '01040334678', + phone: '01040334678', + user: { + username: '홍길동', + phone: '01012345678' + } + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() + +/* +{ + receipt_id: '6655069ca691573f1bb9c28a', + order_id: '1716848284697', + price: 1000, + tax_free: 0, + cancelled_price: 0, + cancelled_tax_free: 0, + order_name: '테스트결제', + company_name: '윤태섭', + gateway_url: 'https://gw.bootpay.co.kr', + metadata: {}, + sandbox: true, + pg: '나이스페이먼츠', + method: '계좌자동이체', + method_symbol: 'automatic_transfer_rest', + method_origin: '계좌자동이체', + method_origin_symbol: 'automatic_transfer_rest', + requested_at: '2024-05-28T07:18:04+09:00', + status_locale: '자동결제빌링키발급이전', + currency: 'KRW', + status: 41 +} + + */ \ No newline at end of file From 47f96194d180009e41e4cbc2a5f2ceb388eccde6 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Tue, 28 May 2024 07:47:30 +0900 Subject: [PATCH 071/117] =?UTF-8?q?=EA=B3=84=EC=A2=8C=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EA=B2=B0=EC=A0=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 604bf68..22f0900 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.3.0 +* 계좌 자동 결제 추가 + ### 2.1.11 * 필드명 back_username -> bank_username 으로 오타 수정 From 93c2f81e823176b56b212f336b2e99033b22a3be Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Tue, 28 May 2024 07:54:02 +0900 Subject: [PATCH 072/117] example update --- test/requestSubscribeAutomaticTransferBillingKey.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/requestSubscribeAutomaticTransferBillingKey.js b/test/requestSubscribeAutomaticTransferBillingKey.js index 05a9579..fac5caa 100644 --- a/test/requestSubscribeAutomaticTransferBillingKey.js +++ b/test/requestSubscribeAutomaticTransferBillingKey.js @@ -11,12 +11,12 @@ order_name: '테스트결제', subscription_id: (new Date()).getTime(), price: 1000, - username: '윤태섭', + username: '홍길동', bank_name: '국민', - bank_account: '67560101092472', - identity_no: '861014', - cash_receipt_identity_no: '01040334678', - phone: '01040334678', + bank_account: '67561234123492472', + identity_no: '901014', + cash_receipt_identity_no: '01012341234', + phone: '01012341234', user: { username: '홍길동', phone: '01012345678' From 4ffac544b9d400323e2c17fe907e073c97078952 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Wed, 29 May 2024 16:21:04 +0900 Subject: [PATCH 073/117] =?UTF-8?q?*=20requestSubscribePayment=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 +++ package.json | 2 +- src/bootpay.ts | 19 ++++++++++++++++++- src/lib/response.ts | 19 +++++++++++++++++++ test/subscribePayment.js | 20 ++++++++++++++++++++ 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 test/subscribePayment.js diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 22f0900..cc1290b 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.3.1 +* requestSubscribePayment 함수 추가 + ### 2.3.0 * 계좌 자동 결제 추가 diff --git a/package.json b/package.json index 88f577e..951446b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.3.0", + "version": "2.3.1", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", diff --git a/src/bootpay.ts b/src/bootpay.ts index 5c07096..1b93eae 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -19,7 +19,7 @@ import { RequestCashReceiptParameters, CancelCashReceiptParameters, RequestAuthenticateParameters, - SubscribePaymentLookupResponse, SubscriptionBillingTransferRequestParameters + SubscribePaymentLookupResponse, SubscriptionBillingTransferRequestParameters, SubscriptionPaymentRequestParameters } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -175,6 +175,23 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { } } + /** + * requestSubscribePayment + * Comment by ehowlsla + * @param subscriptionRequest: SubscriptionPaymentRequestParameters + * @returns Promise + */ + async requestSubscribePayment(subscriptionRequest: SubscriptionPaymentRequestParameters): Promise { + try { + const response: ReceiptResponseParameters = await this.post('subscribe/payment', { + ...subscriptionRequest + }) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + /** * destroyBillingKey * Comment by GOSOMI diff --git a/src/lib/response.ts b/src/lib/response.ts index 767d991..c6d8f87 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -248,6 +248,25 @@ export interface SubscriptionCardPaymentRequestParameters { card_quota?: string card_interest?: string order_id: string + feedback_url?: string + content_type?: string + metadata?: any + items?: Array + user?: UserModel + extra?: ExtraModel +} + +export interface SubscriptionPaymentRequestParameters { + billing_key: string + order_name: string + price: number + tax_free?: number + card_quota?: string + card_interest?: string + order_id: string + feedback_url?: string + content_type?: string + metadata?: any items?: Array user?: UserModel extra?: ExtraModel diff --git a/test/subscribePayment.js b/test/subscribePayment.js new file mode 100644 index 0000000..436d99f --- /dev/null +++ b/test/subscribePayment.js @@ -0,0 +1,20 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestSubscribePayment({ + billing_key: '62b3d166cf9f6d001bd20d59', + order_name: '테스트 결제', + order_id: (new Date()).getTime(), + price: 100, + tax_free: 0 + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file From 29fb5e04104d6d7febf113bd1bca262be5bb7e04 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Wed, 5 Jun 2024 17:00:20 +0900 Subject: [PATCH 074/117] =?UTF-8?q?=EB=B0=B0=EC=86=A1=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?api=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 +++ package.json | 2 +- src/lib/response.ts | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index cc1290b..a940f56 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.3.2 +* 배송등록 api 필드 추가 + ### 2.3.1 * requestSubscribePayment 함수 추가 diff --git a/package.json b/package.json index 951446b..a500561 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.3.1", + "version": "2.3.2", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", diff --git a/src/lib/response.ts b/src/lib/response.ts index c6d8f87..9f58f20 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -306,6 +306,7 @@ export interface SubscribePaymentReserveParameters { export interface ShippingRequestParameters { receipt_id: string + receipt_url: string tracking_number: string delivery_corp: string shipping_prepayment?: boolean From 61a094c96b63c6aa175241599a571324387b7eb4 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Thu, 18 Jul 2024 17:58:18 +0900 Subject: [PATCH 075/117] readme update --- README.md | 133 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 94 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index b17d6c1..08a906e 100644 --- a/README.md +++ b/README.md @@ -9,29 +9,32 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 * PG 결제창 연동은 클라이언트 라이브러리에서 수행됩니다. (Javascript, Android, iOS, React Native, Flutter 등) * 결제 검증 및 취소, 빌링키 발급, 본인인증 등의 수행은 서버사이드에서 진행됩니다. (Java, PHP, Python, Ruby, Node.js, Go, ASP.NET 등) - -## 기능 -1. (부트페이 통신을 위한) 토큰 발급 -2. 결제 단건 조회 -3. 결제 취소 (전액 취소 / 부분 취소) -4. 신용카드 자동결제 (빌링결제) - - 4-1. 빌링키 발급 - - 4-2. 발급된 빌링키로 결제 승인 요청 - - 4-3. 발급된 빌링키로 결제 예약 요청 - - 4-4. 발급된 빌링키로 결제 예약 - 취소 요청 - - 4-5. 빌링키 삭제 - - 4-6. 빌링키 조회 - -5. (생체인증, 비밀번호 결제를 위한) 구매자 토큰 발급 -6. 서버 승인 요청 -7. 본인 인증 결과 조회 -8. (에스크로 이용시) PG사로 배송정보 보내기 +## 목차 +- [사용하기](#사용하기) + - [1. 토큰 발급](#1-토큰-발급) + - [2. 결제 단건 조회](#2-결제-단건-조회) + - [3. 결제 취소 (전액 취소 / 부분 취소)](#3-결제-취소-전액-취소--부분-취소) + - [4. 자동/빌링/정기 결제](#4-자동빌링정기-결제) + - [4-1. 카드 빌링키 발급](#4-1-카드-빌링키-발급) + - [4-2. 계좌 빌링키 발급](#4-2-계좌-빌링키-발급) + - [4-3. 결제 요청하기](#4-3-결제-요청하기) + - [4-4. 결제 예약하기](#4-4-결제-예약하기) + - [4-5. 예약 조회하기](#4-5-예약-조회하기) + - [4-6. 예약 취소하기](#4-6-예약-취소하기) + - [4-7. 빌링키 삭제하기](#4-7-빌링키-삭제하기) + - [4-8. 빌링키 조회하기](#4-8-빌링키-조회하기) + - [5. 회원 토큰 발급요청](#5-회원-토큰-발급요청) + - [6. 서버 승인 요청](#6-서버-승인-요청) + - [7. 본인 인증 결과 조회](#7-본인-인증-결과-조회) + - [8. 에스크로 이용시 PG사로 배송정보 보내기](#8-에스크로-이용시-pg사로-배송정보-보내기) + - [9-1. 현금영수증 발행하기](#9-1-현금영수증-발행하기) + - [9-2. 현금영수증 발행 취소](#9-2-현금영수증-발행-취소) + - [9-3. 별건 현금영수증 발행](#9-3-별건-현금영수증-발행) + - [9-4. 별건 현금영수증 발행 취소](#9-4-별건-현금영수증-발행-취소) +- [Example 프로젝트](#example-프로젝트) +- [Documentation](#documentation) +- [기술문의](#기술문의) +- [License](#license) ## npm으로 설치하기 @@ -67,7 +70,7 @@ import { Bootpay } from "@bootpay/backend-js"; ``` -## 1. (부트페이 통신을 위한) 토큰 발급 +## 1. 토큰 발급 부트페이와 서버간 통신을 하기 위해서는 부트페이 서버로부터 토큰을 발급받아야 합니다. 발급된 토큰은 30분간 유효하며, 최초 발급일로부터 30분이 지날 경우 토큰 발급 함수를 재호출 해주셔야 합니다. @@ -139,7 +142,8 @@ price를 지정하지 않으면 전액취소 됩니다. })() ``` -## 4-1. 빌링키 발급 +## 4. 자동/빌링/정기 결제 +## 4-1. 카드 빌링키 발급 REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에게 빌링키를 발급받을 수 있습니다. 발급받은 빌링키를 저장하고 있다가, 원하는 시점, 원하는 금액에 결제 승인 요청하여 좀 더 자유로운 결제시나리오에 적용이 가능합니다. * 비인증 정기결제(REST API) 방식을 지원하는 PG사만 사용 가능합니다. @@ -171,8 +175,44 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 } })() ``` - -## 4-2. 발급된 빌링키로 결제 승인 요청 + +## 4-2. 계좌 빌링키 발급 +발급된 빌링키로 원하는 시점에 원하는 금액으로 결제 승인 요청을 할 수 있습니다. 잔액이 부족하거나 도난 카드 등의 특별한 건이 아니면 PG사에서 결제를 바로 승인합니다. +```javascript +(async () => { + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestSubscribeAutomaticTransferBillingKey({ + pg: '나이스페이', + order_name: '테스트결제', + subscription_id: (new Date()).getTime(), + price: 1000, + username: '홍길동', + bank_name: '국민', + bank_account: '67561234123492472', + identity_no: '901014', + cash_receipt_identity_no: '01012341234', + phone: '01012341234', + user: { + username: '홍길동', + phone: '01012345678' + } + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() + + +``` + + +## 4-3. 결제 요청하기 발급된 빌링키로 원하는 시점에 원하는 금액으로 결제 승인 요청을 할 수 있습니다. 잔액이 부족하거나 도난 카드 등의 특별한 건이 아니면 PG사에서 결제를 바로 승인합니다. ```javascript @@ -196,8 +236,8 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 } })() ``` -## 4-3. 발급된 빌링키로 결제 예약 요청 -원하는 시점에 4-1로 결제 승인 요청을 보내도 되지만, 빌링키 발급 이후에 바로 결제 예약 할 수 있습니다. (빌링키당 최대 10건) +## 4-4. 결제 예약하기 +발급된 빌링키로 결제를 예약합니다. (빌링키당 최대 10건) ```javascript (async () => { Bootpay.setConfiguration({ @@ -221,8 +261,16 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 })() ``` -## 4-4. 발급된 빌링키로 결제 예약 - 취소 요청 -빌링키로 예약된 결제건을 취소합니다. +## 4-5. 예약 조회하기 +예약시 응답받은 reserveId로 예약된 건을 조회합니다. +```javascript +const reserve_id = "5b8f6a4d396fa665fdc2b5ea" +await Bootpay.subscribePaymentReserveLookup(reserve_id) +``` + + +## 4-6. 예약 취소하기 +예약시 응답받은 reserveId로 예약된 건을 취소합니다. ```javascript (async () => { Bootpay.setConfiguration({ @@ -249,8 +297,8 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 })() ``` -## 4-5. 빌링키 삭제 -발급된 빌링키로 더 이상 사용되지 않도록, 삭제 요청합니다. +## 4-7. 빌링키 삭제하기 +발급된 빌링키를 삭제합니다. 삭제하더라도 예약된 결제건은 취소되지 않습니다. 예약된 결제건 취소를 원하시면 예약 취소하기를 요청하셔야 합니다. ```javascript (async () => { Bootpay.setConfiguration({ @@ -267,8 +315,9 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 })() ``` -## 4-6. 빌링키 조회 -(빌링키 발급 완료시 리턴받았던 receipt_id에 한정) 어떤 빌링키였는지 조회합니다. +## 4-8. 빌링키 조회하기 +클라이언트에서 빌링키 발급시, 보안상 클라이언트 이벤트에 빌링키를 전달해주지 않습니다. 그러므로 이 API를 통해 조회해야 합니다. +다음은 빌링키 발급 요청했던 receiptId 로 빌링키를 조회합니다. ```javascript (async () => { Bootpay.setConfiguration({ @@ -285,10 +334,16 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 })() ``` +아래는 billingKey로 조회합니다. +```javascript +const response = await Bootpay.lookupBillingKey('66542dfb4d18d5fc7b43e1b6') +console.log(response) +``` + -## 5. 사용자 토큰 발급 -(부트페이 단독) 부트페이에서 제공하는 간편결제창, 생체인증 기반의 결제 사용을 위해서는 개발사에서 회원 고유번호를 관리해야하며, 해당 회원에 대한 사용자 토큰을 발급합니다. -이 토큰값을 기반으로 클라이언트에서 결제요청 하시면 되겠습니다. +## 5. 회원 토큰 발급요청 +ㅇㅇ페이 사용을 위해 가맹점 회원의 토큰을 발급합니다. 가맹점은 회원의 고유번호를 관리해야합니다. +이 토큰값을 기반으로 클라이언트에서 결제요청(payload.user_token) 하시면 되겠습니다. ```javascript (async () => { Bootpay.setConfiguration({ @@ -389,7 +444,7 @@ PG사로 배송정보( 이니시스, KCP만 지원 )를 보내서 에스크로 ## Documentation -[부트페이 개발매뉴얼](https://docs.bootpay.co.kr/next/)을 참조해주세요 +[부트페이 개발매뉴얼](https://developer.bootpay.co.kr/)을 참조해주세요 ## 기술문의 From 6416f1d4ca956df37032e96b94ccf0f8b76edb0f Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Fri, 19 Jul 2024 09:37:16 +0900 Subject: [PATCH 076/117] readme update --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 08a906e..1678f14 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,8 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ``` ## 4-2. 계좌 빌링키 발급 -발급된 빌링키로 원하는 시점에 원하는 금액으로 결제 승인 요청을 할 수 있습니다. 잔액이 부족하거나 도난 카드 등의 특별한 건이 아니면 PG사에서 결제를 바로 승인합니다. +REST API 방식으로 고객의 계좌 정보를 전달하여, PG사에게 빌링키 발급을 요청합니다. 요청 후 빌링키가 바로 발급되진 않고, 출금동의 확인 절차까지 진행해야 빌링키가 발급됩니다. +먼저 빌링키를 요청합니다. ```javascript (async () => { Bootpay.setConfiguration({ @@ -208,10 +209,21 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 } })() +``` +이후 빌링키 발급 요청시 응답받은 receipt_id로, 출금 동의 확인을 요청합니다. +```javascript +try { + await Bootpay.getAccessToken() + const response = await Bootpay.publishAutomaticTransferBillingKey('6655069ca691573f1bb9c28a') + console.log(response) +} catch (e) { + console.log(e) +} ``` + ## 4-3. 결제 요청하기 발급된 빌링키로 원하는 시점에 원하는 금액으로 결제 승인 요청을 할 수 있습니다. 잔액이 부족하거나 도난 카드 등의 특별한 건이 아니면 PG사에서 결제를 바로 승인합니다. From bed0c463cc65cd2324a198c63822ae224e173643 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Sun, 16 Mar 2025 14:35:32 +0900 Subject: [PATCH 077/117] =?UTF-8?q?wallet=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bootpay.ts | 40 ++++++++++++++- src/lib/response.ts | 86 ++++++++++++++++++++++++++++++++- test/getUserWallets.js | 17 +++++++ test/lookupSubscribeBilling.js | 6 +-- test/subscribePaymentReserve.js | 1 + test/walletPayment.js | 25 ++++++++++ 6 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 test/getUserWallets.js create mode 100644 test/walletPayment.js diff --git a/src/bootpay.ts b/src/bootpay.ts index 1b93eae..186681a 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -19,7 +19,10 @@ import { RequestCashReceiptParameters, CancelCashReceiptParameters, RequestAuthenticateParameters, - SubscribePaymentLookupResponse, SubscriptionBillingTransferRequestParameters, SubscriptionPaymentRequestParameters + SubscribePaymentLookupResponse, + SubscriptionBillingTransferRequestParameters, + SubscriptionPaymentRequestParameters, + WalletRequestParameters, WalletDataPart, WalletPaymentResponseParameters } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -422,6 +425,41 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { return Promise.reject(e) } } + + /** + * 등록된 지갑 리스트 가져오기 + * Comment by ehowlsla + * @date: 2025-03-16 + */ + async getUserWallets(user_id: string, sandbox: boolean): Promise { + try { + const queryParams = new URLSearchParams({ user_id, sandbox: sandbox.toString() }).toString(); + const response: WalletDataPart[] = await this.get(`wallet?${queryParams}`); + return Promise.resolve(response) + } catch (error) { + return Promise.reject(error) + } + } + + // async getUserWallets(user_id: string, sandbox: boolean) { + // try { + // const response: WalletDataPart[] = await this.get(`wallet?user_id=${user_id}&sandbox=${sandbox}`) + // return Promise.resolve(response) + // } catch (e) { + // return Promise.reject(e) + // } + // } + + async requestWalletPayment(walletRequest: WalletRequestParameters) { + try { + const response: WalletPaymentResponseParameters = await this.post('wallet/payment', { + ...walletRequest + }) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } } const Bootpay: BootpayBackendNodejs = new BootpayBackendNodejs() diff --git a/src/lib/response.ts b/src/lib/response.ts index 9f58f20..3a9f22a 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -395,4 +395,88 @@ export interface SubscribePaymentLookupResponse { reserve_finished_at: string reserve_revoked_at: string status: number -} \ No newline at end of file +} + +export interface WalletRequestParameters { + user_id: string + order_name: string + price: number + tax_free?: number + order_id: string + webhook_url?: string + content_type?: 'application/json' | 'application/x-www-form-urlencoded' + // order_id?: string + items?: ItemModel + user?: UserModel + extra?: ExtraModel + metadata?: object + sandbox: boolean +} + +export interface WalletPaymentResponseParameters { + cancelled_price: number + wallet_data: WalletData + metadata: Record + cancelled_tax_free: number + method: string + card_data: CardData + sandbox: boolean + receipt_id: string + method_origin: string + order_name: string + method_origin_symbol: string + receipt_url: string + method_symbol: string + purchased_at: string + tax_free: number + price: number + company_name: string + pg: string + status_locale: string + currency: string + http_status: number + order_id: number + requested_at: string + status: number +} + +export interface WalletData { + success: WalletDataPart + failure: any[] // 실패한 데이터가 리스트 형태로 존재 +} + +export interface WalletDataPart { + wallet_id: string + type: number + sandbox: number + order: number + payment_status: number + batch_data: BatchData + card_code: string + expired_at: string + latest_purchased_at: string +} + +export interface BatchData { + card_no: string + card_company: string + card_company_code: string + card_type: number + card_hash: string +} + +export interface CardData { + tid: string + card_approve_no: string + card_no: string + card_quota: string + card_company_code: string + card_company: string + card_type?: string +} + +// public class WalletResponseData { +// +// List data; +// int http_status; +// } \ No newline at end of file diff --git a/test/getUserWallets.js b/test/getUserWallets.js new file mode 100644 index 0000000..d8da56a --- /dev/null +++ b/test/getUserWallets.js @@ -0,0 +1,17 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.getUserWallets( + 'bootpay', + true + ) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/lookupSubscribeBilling.js b/test/lookupSubscribeBilling.js index 9267444..c904279 100644 --- a/test/lookupSubscribeBilling.js +++ b/test/lookupSubscribeBilling.js @@ -1,12 +1,12 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + application_id: '6784d481a3175898bd6e5494', + private_key: 'cznW8pZU8f60PAT2p/VUyJidiz0PdKXiro2LikyZnH4=' }) try { await Bootpay.getAccessToken() - const response = await Bootpay.lookupSubscribeBillingKey('62b3cbbecf9f6d001bd20ce8') + const response = await Bootpay.lookupSubscribeBillingKey('67a1faaf54c6b5ba3bc0b98d') console.log(response) } catch (e) { console.log(e) diff --git a/test/subscribePaymentReserve.js b/test/subscribePaymentReserve.js index db51df1..ba0f2fc 100644 --- a/test/subscribePaymentReserve.js +++ b/test/subscribePaymentReserve.js @@ -14,6 +14,7 @@ price: 1000, reserve_execute_at: new Date((new Date()).getTime() + 5000) }) + console.log(response) } catch (e) { console.log(e) diff --git a/test/walletPayment.js b/test/walletPayment.js new file mode 100644 index 0000000..90beca8 --- /dev/null +++ b/test/walletPayment.js @@ -0,0 +1,25 @@ +(async () => { + const Bootpay = require('../dist/bootpay.js').Bootpay + Bootpay.setConfiguration({ + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.requestWalletPayment({ + user_id: 'bootpay', + order_name: '테스트 결제', + order_id: (new Date()).getTime(), + price: 100, + sandbox: true, + user: { + phone: '01012341234', + username: '홍길동', + email: 'test@bootpay.co.kr' + } + }) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file From 09e8c9d8162c4e696295f436d70b30b4b042c738 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Sun, 16 Mar 2025 14:35:45 +0900 Subject: [PATCH 078/117] =?UTF-8?q?wallet=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index a940f56..9c8bd87 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.3.3 +* wallet api 추가 + ### 2.3.2 * 배송등록 api 필드 추가 diff --git a/package.json b/package.json index a500561..ee50003 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.3.2", + "version": "2.3.3", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", From 179b722d39b8ce6b26bf33dc02dad0f2d63d5138 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Mon, 17 Mar 2025 21:52:51 +0900 Subject: [PATCH 079/117] * walletPayment response type bug fixed --- CHNAGELOG.md | 3 +++ package.json | 2 +- src/lib/response.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 9c8bd87..0183e55 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.3.5 +* walletPayment response type bug fixed + ### 2.3.3 * wallet api 추가 diff --git a/package.json b/package.json index ee50003..c3a748d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.3.3", + "version": "2.3.5", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", diff --git a/src/lib/response.ts b/src/lib/response.ts index 3a9f22a..bc6e769 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -435,7 +435,7 @@ export interface WalletPaymentResponseParameters { status_locale: string currency: string http_status: number - order_id: number + order_id: string requested_at: string status: number } From 58cc85259e4f6f7527b9ca71237792c4b528959a Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 22 May 2025 19:59:04 +0900 Subject: [PATCH 080/117] =?UTF-8?q?client=5Fip=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EB=90=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/response.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/response.ts b/src/lib/response.ts index 9f58f20..2ea3349 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -368,6 +368,7 @@ export interface RequestAuthenticateParameters { identity_no: string carrier: string phone: string + client_ip: string site_url?: string authenticate_type?: 'sms' | 'pass' order_name: string From 7f03f2d0005bf1ec72b9de5011000b0c1109da42 Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 22 May 2025 19:59:46 +0900 Subject: [PATCH 081/117] =?UTF-8?q?2.3.6=20=EB=B0=B0=ED=8F=AC=20=EC=A4=80?= =?UTF-8?q?=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 35 +++++++++++++++++++++++++---------- package.json | 2 +- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 0183e55..9d3d8c9 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,35 +1,50 @@ +### 2.3.65 + +* 본인인증 REST API로 요청시 client_ip 파라메터 필수 추가 + ### 2.3.5 + * walletPayment response type bug fixed ### 2.3.3 -* wallet api 추가 + +* wallet api 추가 ### 2.3.2 -* 배송등록 api 필드 추가 + +* 배송등록 api 필드 추가 ### 2.3.1 -* requestSubscribePayment 함수 추가 + +* requestSubscribePayment 함수 추가 ### 2.3.0 -* 계좌 자동 결제 추가 + +* 계좌 자동 결제 추가 ### 2.1.11 + * 필드명 back_username -> bank_username 으로 오타 수정 ### 2.1.4 -* 날짜 타입을 string -> Date 로 명시적으로 수정 + +* 날짜 타입을 string -> Date 로 명시적으로 수정 ### 2.1.3 -* 정기결제요청시 feedback_url, metadata, content_type 파라미터 정의 추가 + +* 정기결제요청시 feedback_url, metadata, content_type 파라미터 정의 추가 ### 2.1.2 -* 버전 재배포 + +* 버전 재배포 ### 2.1.1 -* 정기결제 예약시 order_id 파라미터 정의 추가 -### 2.1.0 -* 결제취소 요청시 refund optional 로 수정 +* 정기결제 예약시 order_id 파라미터 정의 추가 + +### 2.1.0 + +* 결제취소 요청시 refund optional 로 수정 ### 2.0.9 ( Stable ) diff --git a/package.json b/package.json index c3a748d..69f91c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.3.5", + "version": "2.3.6", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", From 93e1f24558e927b1a70f267412c48b7890e4f2f6 Mon Sep 17 00:00:00 2001 From: gosomi Date: Thu, 22 May 2025 20:02:39 +0900 Subject: [PATCH 082/117] =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=98=A4=ED=83=80?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 9d3d8c9..932db00 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,4 +1,4 @@ -### 2.3.65 +### 2.3.6 * 본인인증 REST API로 요청시 client_ip 파라메터 필수 추가 From 248afe767bb2099bed6e2fbbb983fda3533a3468 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Wed, 26 Nov 2025 17:09:43 +0900 Subject: [PATCH 083/117] bootpay commerce added --- src/bootpay-commerce.ts | 156 +++++++++++++ src/bootpay.ts | 1 + src/lib/commerce-resource.ts | 209 ++++++++++++++++++ src/lib/commerce/modules/index.ts | 9 + src/lib/commerce/modules/invoice.ts | 51 +++++ src/lib/commerce/modules/order-cancel.ts | 73 ++++++ .../modules/order-subscription-adjustment.ts | 53 +++++ .../modules/order-subscription-bill.ts | 51 +++++ .../commerce/modules/order-subscription.ts | 129 +++++++++++ src/lib/commerce/modules/order.ts | 62 ++++++ src/lib/commerce/modules/product.ts | 110 +++++++++ src/lib/commerce/modules/user-group.ts | 93 ++++++++ src/lib/commerce/modules/user.ts | 100 +++++++++ src/lib/commerce/types/common.ts | 23 ++ src/lib/commerce/types/index.ts | 10 + src/lib/commerce/types/invoice.ts | 110 +++++++++ src/lib/commerce/types/order-cancel.ts | 48 ++++ .../types/order-subscription-adjustment.ts | 22 ++ .../commerce/types/order-subscription-bill.ts | 68 ++++++ src/lib/commerce/types/order-subscription.ts | 101 +++++++++ src/lib/commerce/types/order.ts | 65 ++++++ src/lib/commerce/types/product.ts | 144 ++++++++++++ src/lib/commerce/types/user-group.ts | 78 +++++++ src/lib/commerce/types/user.ts | 83 +++++++ test/commerce/getAccessToken.js | 22 ++ test/commerce/invoiceCreate.js | 25 +++ test/commerce/invoiceDetail.js | 20 ++ test/commerce/invoiceList.js | 29 +++ test/commerce/invoiceNotify.js | 21 ++ test/commerce/orderCancelApprove.js | 23 ++ test/commerce/orderCancelList.js | 33 +++ test/commerce/orderCancelReject.js | 23 ++ test/commerce/orderCancelRequest.js | 24 ++ test/commerce/orderCancelWithdraw.js | 20 ++ test/commerce/orderDetail.js | 20 ++ test/commerce/orderList.js | 33 +++ test/commerce/orderMonth.js | 20 ++ .../orderSubscriptionAdjustmentCreate.js | 27 +++ .../orderSubscriptionAdjustmentDelete.js | 23 ++ .../orderSubscriptionAdjustmentUpdate.js | 25 +++ test/commerce/orderSubscriptionBillDetail.js | 20 ++ test/commerce/orderSubscriptionBillList.js | 31 +++ test/commerce/orderSubscriptionBillUpdate.js | 24 ++ ...rderSubscriptionCalculateTerminationFee.js | 29 +++ test/commerce/orderSubscriptionDetail.js | 20 ++ test/commerce/orderSubscriptionList.js | 34 +++ test/commerce/orderSubscriptionPause.js | 23 ++ test/commerce/orderSubscriptionResume.js | 22 ++ test/commerce/orderSubscriptionTermination.js | 23 ++ test/commerce/orderSubscriptionUpdate.js | 23 ++ test/commerce/productCreate.js | 39 ++++ test/commerce/productDelete.js | 20 ++ test/commerce/productDetail.js | 20 ++ test/commerce/productList.js | 32 +++ test/commerce/productStatus.js | 23 ++ test/commerce/productUpdate.js | 25 +++ test/commerce/userAuthenticationData.js | 20 ++ test/commerce/userCheckExist.js | 29 +++ test/commerce/userDelete.js | 20 ++ test/commerce/userDetail.js | 20 ++ .../commerce/userGroupAggregateTransaction.js | 24 ++ test/commerce/userGroupCreate.js | 24 ++ test/commerce/userGroupDetail.js | 20 ++ test/commerce/userGroupLimit.js | 24 ++ test/commerce/userGroupList.js | 29 +++ test/commerce/userGroupUpdate.js | 24 ++ test/commerce/userGroupUserCreate.js | 23 ++ test/commerce/userGroupUserDelete.js | 23 ++ test/commerce/userJoin.js | 26 +++ test/commerce/userList.js | 29 +++ test/commerce/userLogin.js | 20 ++ test/commerce/userToken.js | 20 ++ test/commerce/userUpdate.js | 24 ++ tsconfig.json | 4 +- 74 files changed, 3046 insertions(+), 2 deletions(-) create mode 100644 src/bootpay-commerce.ts create mode 100644 src/lib/commerce-resource.ts create mode 100644 src/lib/commerce/modules/index.ts create mode 100644 src/lib/commerce/modules/invoice.ts create mode 100644 src/lib/commerce/modules/order-cancel.ts create mode 100644 src/lib/commerce/modules/order-subscription-adjustment.ts create mode 100644 src/lib/commerce/modules/order-subscription-bill.ts create mode 100644 src/lib/commerce/modules/order-subscription.ts create mode 100644 src/lib/commerce/modules/order.ts create mode 100644 src/lib/commerce/modules/product.ts create mode 100644 src/lib/commerce/modules/user-group.ts create mode 100644 src/lib/commerce/modules/user.ts create mode 100644 src/lib/commerce/types/common.ts create mode 100644 src/lib/commerce/types/index.ts create mode 100644 src/lib/commerce/types/invoice.ts create mode 100644 src/lib/commerce/types/order-cancel.ts create mode 100644 src/lib/commerce/types/order-subscription-adjustment.ts create mode 100644 src/lib/commerce/types/order-subscription-bill.ts create mode 100644 src/lib/commerce/types/order-subscription.ts create mode 100644 src/lib/commerce/types/order.ts create mode 100644 src/lib/commerce/types/product.ts create mode 100644 src/lib/commerce/types/user-group.ts create mode 100644 src/lib/commerce/types/user.ts create mode 100644 test/commerce/getAccessToken.js create mode 100644 test/commerce/invoiceCreate.js create mode 100644 test/commerce/invoiceDetail.js create mode 100644 test/commerce/invoiceList.js create mode 100644 test/commerce/invoiceNotify.js create mode 100644 test/commerce/orderCancelApprove.js create mode 100644 test/commerce/orderCancelList.js create mode 100644 test/commerce/orderCancelReject.js create mode 100644 test/commerce/orderCancelRequest.js create mode 100644 test/commerce/orderCancelWithdraw.js create mode 100644 test/commerce/orderDetail.js create mode 100644 test/commerce/orderList.js create mode 100644 test/commerce/orderMonth.js create mode 100644 test/commerce/orderSubscriptionAdjustmentCreate.js create mode 100644 test/commerce/orderSubscriptionAdjustmentDelete.js create mode 100644 test/commerce/orderSubscriptionAdjustmentUpdate.js create mode 100644 test/commerce/orderSubscriptionBillDetail.js create mode 100644 test/commerce/orderSubscriptionBillList.js create mode 100644 test/commerce/orderSubscriptionBillUpdate.js create mode 100644 test/commerce/orderSubscriptionCalculateTerminationFee.js create mode 100644 test/commerce/orderSubscriptionDetail.js create mode 100644 test/commerce/orderSubscriptionList.js create mode 100644 test/commerce/orderSubscriptionPause.js create mode 100644 test/commerce/orderSubscriptionResume.js create mode 100644 test/commerce/orderSubscriptionTermination.js create mode 100644 test/commerce/orderSubscriptionUpdate.js create mode 100644 test/commerce/productCreate.js create mode 100644 test/commerce/productDelete.js create mode 100644 test/commerce/productDetail.js create mode 100644 test/commerce/productList.js create mode 100644 test/commerce/productStatus.js create mode 100644 test/commerce/productUpdate.js create mode 100644 test/commerce/userAuthenticationData.js create mode 100644 test/commerce/userCheckExist.js create mode 100644 test/commerce/userDelete.js create mode 100644 test/commerce/userDetail.js create mode 100644 test/commerce/userGroupAggregateTransaction.js create mode 100644 test/commerce/userGroupCreate.js create mode 100644 test/commerce/userGroupDetail.js create mode 100644 test/commerce/userGroupLimit.js create mode 100644 test/commerce/userGroupList.js create mode 100644 test/commerce/userGroupUpdate.js create mode 100644 test/commerce/userGroupUserCreate.js create mode 100644 test/commerce/userGroupUserDelete.js create mode 100644 test/commerce/userJoin.js create mode 100644 test/commerce/userList.js create mode 100644 test/commerce/userLogin.js create mode 100644 test/commerce/userToken.js create mode 100644 test/commerce/userUpdate.js diff --git a/src/bootpay-commerce.ts b/src/bootpay-commerce.ts new file mode 100644 index 0000000..d44d10b --- /dev/null +++ b/src/bootpay-commerce.ts @@ -0,0 +1,156 @@ +import { BootpayCommerceResource, CommerceConfiguration, BootpayCommerceResponse } from './lib/commerce-resource' +import { UserModule } from './lib/commerce/modules/user' +import { UserGroupModule } from './lib/commerce/modules/user-group' +import { ProductModule } from './lib/commerce/modules/product' +import { InvoiceModule } from './lib/commerce/modules/invoice' +import { OrderModule } from './lib/commerce/modules/order' +import { OrderCancelModule } from './lib/commerce/modules/order-cancel' +import { OrderSubscriptionModule } from './lib/commerce/modules/order-subscription' +import { OrderSubscriptionBillModule } from './lib/commerce/modules/order-subscription-bill' +import { OrderSubscriptionAdjustmentModule } from './lib/commerce/modules/order-subscription-adjustment' + +export interface CommerceTokenResponse { + access_token: string + expired_at?: string +} + +export class BootpayCommerce extends BootpayCommerceResource { + public user!: UserModule + public userGroup!: UserGroupModule + public product!: ProductModule + public invoice!: InvoiceModule + public order!: OrderModule + public orderCancel!: OrderCancelModule + public orderSubscription!: OrderSubscriptionModule + public orderSubscriptionBill!: OrderSubscriptionBillModule + public orderSubscriptionAdjustment!: OrderSubscriptionAdjustmentModule + + constructor(configuration?: CommerceConfiguration) { + super() + if (configuration) { + this.setConfiguration(configuration) + } + this.initModules() + } + + private initModules(): void { + this.user = new UserModule(this) + this.userGroup = new UserGroupModule(this) + this.product = new ProductModule(this) + this.invoice = new InvoiceModule(this) + this.order = new OrderModule(this) + this.orderCancel = new OrderCancelModule(this) + this.orderSubscription = new OrderSubscriptionModule(this) + this.orderSubscriptionBill = new OrderSubscriptionBillModule(this) + this.orderSubscriptionAdjustment = new OrderSubscriptionAdjustmentModule(this) + } + + /** + * 액세스 토큰 발급 + * client_key/secret_key로 인증 + */ + async getAccessToken(): Promise> { + try { + const { client_key, secret_key } = this.commerceConfiguration + + const response = await this.postWithBasicAuth('request/token', { + client_key, + secret_key + }) + + if (response.success && response.data?.access_token) { + this.setToken(response.data.access_token) + } + + return response + } catch (e: any) { + return Promise.reject(e) + } + } + + /** + * 토큰을 발급받아 설정합니다. (메서드 체이닝 지원) + */ + async withToken(): Promise { + await this.getAccessToken() + return this + } + + /** + * 현재 설정된 토큰을 반환합니다. + */ + getCurrentToken(): string | undefined { + return this.getToken() + } + + /** + * 토큰이 설정되어 있는지 확인합니다. + */ + hasToken(): boolean { + const token = this.getToken() + return token !== undefined && token !== '' + } + + /** + * 현재 role을 설정합니다. (메서드 체이닝 지원) + * @param role 설정할 role + */ + withRole(role: string): BootpayCommerce { + this.setRole(role) + return this + } + + /** + * 일반 사용자 role로 설정합니다. + */ + asUser(): BootpayCommerce { + return this.withRole('user') + } + + /** + * 매니저 role로 설정합니다. + */ + asManager(): BootpayCommerce { + return this.withRole('manager') + } + + /** + * 파트너 role로 설정합니다. + */ + asPartner(): BootpayCommerce { + return this.withRole('partner') + } + + /** + * 벤더 role로 설정합니다. + */ + asVendor(): BootpayCommerce { + return this.withRole('vendor') + } + + /** + * 슈퍼바이저 role로 설정합니다. + */ + asSupervisor(): BootpayCommerce { + return this.withRole('supervisor') + } + + /** + * 현재 role을 반환합니다. + */ + getCurrentRole(): string { + return this.getRole() + } + + /** + * role을 기본값(user)으로 초기화합니다. + */ + clearRole(): BootpayCommerce { + this.setRole('user') + return this + } +} + +export { BootpayCommerceResource, CommerceConfiguration, BootpayCommerceResponse } +export * from './lib/commerce/types' +export * from './lib/commerce/modules' diff --git a/src/bootpay.ts b/src/bootpay.ts index 186681a..c438028 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -470,3 +470,4 @@ export default Bootpay export * from './lib/response' export * from './lib/resource' +export * from './bootpay-commerce' diff --git a/src/lib/commerce-resource.ts b/src/lib/commerce-resource.ts new file mode 100644 index 0000000..8aa0dc9 --- /dev/null +++ b/src/lib/commerce-resource.ts @@ -0,0 +1,209 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios' + +export interface BootpayCommerceRestApiErrorResponse { + error_code?: number + message?: string +} + +interface CommerceEntrypoints { + development: string + stage: string + production: string +} + +export interface CommerceConfiguration { + client_key?: string + secret_key?: string + mode?: 'development' | 'production' | 'stage' +} + +export interface BootpayCommerceResponse { + http_status: number + success: boolean + data: T + error?: string +} + +export class BootpayCommerceResource { + $http: AxiosInstance + $token?: string + $role: string + mode: 'development' | 'production' | 'stage' + commerceConfiguration: CommerceConfiguration + API_ENTRYPOINTS: CommerceEntrypoints + apiVersion: string = '1.0.0' + sdkVersion: string = '1.0.0' + + constructor() { + this.mode = 'production' + this.$role = 'user' + this.$http = axios.create({ + timeout: 60000 + }) + this.$token = undefined + this.commerceConfiguration = { + client_key: '', + secret_key: '', + mode: 'production' + } + this.API_ENTRYPOINTS = { + development: 'https://dev-api.bootapi.com/v1', + stage: 'https://stage-api.bootapi.com/v1', + production: 'https://api.bootapi.com/v1' + } + + this.$http.interceptors.response.use( + (response: AxiosResponse): any => { + const result: BootpayCommerceResponse = { + http_status: response.status, + success: response.status >= 200 && response.status < 300, + data: response.data + } + return result + }, + (error: any) => { + if (error.response !== undefined) { + return Promise.reject({ + http_status: error.response.status, + success: false, + data: error.response.data, + error: error.response.data?.message || error.message + } as BootpayCommerceResponse) + } else { + return Promise.reject({ + http_status: -100, + success: false, + data: null, + error: `Request Rest Api Failed to Bootpay Commerce Server, ${error.message}` + } as BootpayCommerceResponse) + } + } + ) + + this.$http.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + config.headers.set('Content-Type', 'application/json') + config.headers.set('Accept', 'application/json') + config.headers.set('Accept-Charset', 'utf-8') + config.headers.set('BOOTPAY-SDK-VERSION', this.sdkVersion) + config.headers.set('BOOTPAY-API-VERSION', this.apiVersion) + config.headers.set('BOOTPAY-SDK-TYPE', '301') + config.headers.set('BOOTPAY-ROLE', this.$role || 'user') + + if (this.$token !== undefined) { + config.headers.set('Authorization', `Bearer ${this.$token}`) + } + return config + }, + (error: any) => { + return Promise.reject(error) + } + ) + } + + setConfiguration(configuration: CommerceConfiguration): void { + if (configuration.mode === undefined) { + configuration.mode = 'production' + } + this.commerceConfiguration = configuration + } + + setApiVersion(version: string) { + this.apiVersion = version + } + + setToken(token: string): void { + this.$token = token + } + + getToken(): string | undefined { + return this.$token + } + + setRole(role: string): void { + this.$role = role + } + + getRole(): string { + return this.$role + } + + private getBasicAuthHeader(): string { + const { client_key, secret_key } = this.commerceConfiguration + if (client_key && secret_key) { + const credentials = `${client_key}:${secret_key}` + const encoded = Buffer.from(credentials).toString('base64') + return `Basic ${encoded}` + } + return '' + } + + entrypoints(url: string): string { + const mode = this.commerceConfiguration.mode || 'production' + return [this.API_ENTRYPOINTS[mode], url].join('/') + } + + async get(url: string, config?: AxiosRequestConfig): Promise> { + try { + const response = await this.$http.get(this.entrypoints(url), config) + return Promise.resolve(response as unknown as BootpayCommerceResponse) + } catch (e) { + return Promise.reject(e) + } + } + + async post( + url: string, + data?: D, + config?: AxiosRequestConfig + ): Promise> { + try { + const response = await this.$http.post(this.entrypoints(url), data, config) + return Promise.resolve(response as unknown as BootpayCommerceResponse) + } catch (e) { + return Promise.reject(e) + } + } + + async postWithBasicAuth( + url: string, + data?: D, + config?: AxiosRequestConfig + ): Promise> { + try { + const authConfig: AxiosRequestConfig = { + ...config, + headers: { + ...config?.headers, + Authorization: this.getBasicAuthHeader() + } + } + const response = await this.$http.post(this.entrypoints(url), data, authConfig) + return Promise.resolve(response as unknown as BootpayCommerceResponse) + } catch (e) { + return Promise.reject(e) + } + } + + async put( + url: string, + data?: D, + config?: AxiosRequestConfig + ): Promise> { + try { + const response = await this.$http.put(this.entrypoints(url), data, config) + return Promise.resolve(response as unknown as BootpayCommerceResponse) + } catch (e) { + return Promise.reject(e) + } + } + + async delete(url: string, config?: AxiosRequestConfig): Promise> { + try { + const response = await this.$http.delete(this.entrypoints(url), config) + return Promise.resolve(response as unknown as BootpayCommerceResponse) + } catch (e) { + return Promise.reject(e) + } + } +} diff --git a/src/lib/commerce/modules/index.ts b/src/lib/commerce/modules/index.ts new file mode 100644 index 0000000..3c8cdbd --- /dev/null +++ b/src/lib/commerce/modules/index.ts @@ -0,0 +1,9 @@ +export * from './user' +export * from './user-group' +export * from './product' +export * from './invoice' +export * from './order' +export * from './order-cancel' +export * from './order-subscription' +export * from './order-subscription-bill' +export * from './order-subscription-adjustment' diff --git a/src/lib/commerce/modules/invoice.ts b/src/lib/commerce/modules/invoice.ts new file mode 100644 index 0000000..ffffdfd --- /dev/null +++ b/src/lib/commerce/modules/invoice.ts @@ -0,0 +1,51 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceInvoice, InvoiceListParams } from '../types' +import { ListParams } from '../types/common' + +export class InvoiceModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 청구서 목록 조회 + * @param params 조회 파라미터 + */ + async list(params?: ListParams): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + if (params.keyword) queryParams.append('keyword', params.keyword) + } + const query = queryParams.toString() + return this.bootpay.get<{ items: CommerceInvoice[]; total: number }>(`invoices${query ? `?${query}` : ''}`) + } + + /** + * 청구서 생성 + * @param invoice 청구서 정보 + */ + async create(invoice: CommerceInvoice): Promise> { + return this.bootpay.post('invoices', invoice) + } + + /** + * 청구서 알림 발송 + * @param invoiceId 청구서 ID + * @param sendTypes 발송 타입 배열 (예: [1, 2] - SMS, Email 등) + */ + async notify(invoiceId: string, sendTypes: number[]): Promise> { + return this.bootpay.post(`invoices/${invoiceId}/notify`, { send_types: sendTypes }) + } + + /** + * 청구서 상세 조회 + * @param invoiceId 청구서 ID + */ + async detail(invoiceId: string): Promise> { + return this.bootpay.get(`invoices/${invoiceId}`) + } +} diff --git a/src/lib/commerce/modules/order-cancel.ts b/src/lib/commerce/modules/order-cancel.ts new file mode 100644 index 0000000..0a7743a --- /dev/null +++ b/src/lib/commerce/modules/order-cancel.ts @@ -0,0 +1,73 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { + OrderCancelListParams, + OrderCancelParams, + OrderCancelActionParams, + CommerceOrderCancelRequestHistory +} from '../types' + +export class OrderCancelModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 취소 요청 목록 조회 + * @param params 조회 파라미터 + */ + async list(params?: OrderCancelListParams): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.order_id) queryParams.append('order_id', params.order_id) + if (params.order_number) queryParams.append('order_number', params.order_number) + } + const query = queryParams.toString() + return this.bootpay.get<{ items: CommerceOrderCancelRequestHistory[]; total: number }>(`order/cancel${query ? `?${query}` : ''}`) + } + + /** + * 취소 요청 + * @param params 취소 요청 파라미터 + */ + async request(params: OrderCancelParams): Promise> { + return this.bootpay.post('order/cancel', params) + } + + /** + * 취소 요청 철회 + * @param orderCancelRequestHistoryId 취소 요청 이력 ID + */ + async withdraw(orderCancelRequestHistoryId: string): Promise> { + return this.bootpay.put(`order/cancel/${orderCancelRequestHistoryId}/withdraw`, {}) + } + + /** + * 취소 승인 + * @param params 취소 승인 파라미터 + */ + async approve(params: OrderCancelActionParams): Promise> { + if (!params.order_cancel_request_history_id) { + return Promise.reject({ success: false, error: 'order_cancel_request_history_id is required' }) + } + return this.bootpay.put( + `order/cancel/${params.order_cancel_request_history_id}/approve`, + params + ) + } + + /** + * 취소 거절 + * @param params 취소 거절 파라미터 + */ + async reject(params: OrderCancelActionParams): Promise> { + if (!params.order_cancel_request_history_id) { + return Promise.reject({ success: false, error: 'order_cancel_request_history_id is required' }) + } + return this.bootpay.put( + `order/cancel/${params.order_cancel_request_history_id}/reject`, + params + ) + } +} diff --git a/src/lib/commerce/modules/order-subscription-adjustment.ts b/src/lib/commerce/modules/order-subscription-adjustment.ts new file mode 100644 index 0000000..f7a558c --- /dev/null +++ b/src/lib/commerce/modules/order-subscription-adjustment.ts @@ -0,0 +1,53 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceOrderSubscriptionAdjustment, OrderSubscriptionAdjustmentUpdateParams } from '../types' + +export class OrderSubscriptionAdjustmentModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 정기구독 조정 생성 + * @param orderSubscriptionId 정기구독 ID + * @param adjustment 조정 정보 + */ + async create( + orderSubscriptionId: string, + adjustment: CommerceOrderSubscriptionAdjustment + ): Promise> { + return this.bootpay.post( + `order_subscriptions/${orderSubscriptionId}/adjustments`, + adjustment + ) + } + + /** + * 정기구독 조정 수정 + * @param params 수정 파라미터 + */ + async update(params: OrderSubscriptionAdjustmentUpdateParams): Promise> { + if (!params.order_subscription_id) { + return Promise.reject({ success: false, error: 'order_subscription_id is required' }) + } + return this.bootpay.put( + `order_subscriptions/${params.order_subscription_id}/adjustments`, + params + ) + } + + /** + * 정기구독 조정 삭제 + * @param orderSubscriptionId 정기구독 ID + * @param orderSubscriptionAdjustmentId 조정 ID + */ + async delete( + orderSubscriptionId: string, + orderSubscriptionAdjustmentId: string + ): Promise> { + return this.bootpay.delete( + `order_subscriptions/${orderSubscriptionId}/adjustments?order_subscription_adjustment_id=${orderSubscriptionAdjustmentId}` + ) + } +} diff --git a/src/lib/commerce/modules/order-subscription-bill.ts b/src/lib/commerce/modules/order-subscription-bill.ts new file mode 100644 index 0000000..987b835 --- /dev/null +++ b/src/lib/commerce/modules/order-subscription-bill.ts @@ -0,0 +1,51 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceOrderSubscriptionBill, OrderSubscriptionBillListParams } from '../types' + +export class OrderSubscriptionBillModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 정기구독 청구 목록 조회 + * @param params 조회 파라미터 + */ + async list(params?: OrderSubscriptionBillListParams): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + if (params.keyword) queryParams.append('keyword', params.keyword) + if (params.order_subscription_id) queryParams.append('order_subscription_id', params.order_subscription_id) + if (params.status && params.status.length > 0) { + queryParams.append('status', params.status.join(',')) + } + } + const query = queryParams.toString() + return this.bootpay.get<{ items: CommerceOrderSubscriptionBill[]; total: number }>(`order_subscription_bills${query ? `?${query}` : ''}`) + } + + /** + * 정기구독 청구 상세 조회 + * @param orderSubscriptionBillId 청구 ID + */ + async detail(orderSubscriptionBillId: string): Promise> { + return this.bootpay.get(`order_subscription_bills/${orderSubscriptionBillId}`) + } + + /** + * 정기구독 청구 수정 + * @param orderSubscriptionBill 청구 정보 + */ + async update(orderSubscriptionBill: CommerceOrderSubscriptionBill): Promise> { + if (!orderSubscriptionBill.order_subscription_bill_id) { + return Promise.reject({ success: false, error: 'order_subscription_bill_id is required' }) + } + return this.bootpay.put( + `order_subscription_bills/${orderSubscriptionBill.order_subscription_bill_id}`, + orderSubscriptionBill + ) + } +} diff --git a/src/lib/commerce/modules/order-subscription.ts b/src/lib/commerce/modules/order-subscription.ts new file mode 100644 index 0000000..9f92e8d --- /dev/null +++ b/src/lib/commerce/modules/order-subscription.ts @@ -0,0 +1,129 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { + CommerceOrderSubscription, + OrderSubscriptionListParams, + OrderSubscriptionUpdateParams, + OrderSubscriptionPauseParams, + OrderSubscriptionResumeParams, + OrderSubscriptionTerminationParams, + CalcTerminateFeeResponse +} from '../types' + +export class OrderSubscriptionRequestIngModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 정기구독 일시정지 + * @param params 일시정지 파라미터 + */ + async pause(params: OrderSubscriptionPauseParams): Promise> { + return this.bootpay.post('order_subscriptions/requests/ing/pause', params) + } + + /** + * 정기구독 재개 + * @param params 재개 파라미터 + */ + async resume(params: OrderSubscriptionResumeParams): Promise> { + return this.bootpay.put('order_subscriptions/requests/ing/resume', params) + } + + /** + * 해지 수수료 계산 + * @param orderSubscriptionId 정기구독 ID (선택) + * @param orderNumber 주문번호 (선택) + */ + async calculateTerminationFee( + orderSubscriptionId?: string, + orderNumber?: string + ): Promise> { + if (!orderSubscriptionId && !orderNumber) { + return Promise.reject({ + success: false, + error: 'orderSubscriptionId or orderNumber is required' + }) + } + + const queryParams = new URLSearchParams() + if (orderSubscriptionId) { + queryParams.append('order_subscription_id', orderSubscriptionId) + } else if (orderNumber) { + queryParams.append('order_number', orderNumber) + } + + return this.bootpay.get( + `order_subscriptions/requests/ing/calculate_termination_fee?${queryParams.toString()}` + ) + } + + /** + * 주문번호로 해지 수수료 계산 + * @param orderNumber 주문번호 + */ + async calculateTerminationFeeByOrderNumber( + orderNumber: string + ): Promise> { + return this.calculateTerminationFee(undefined, orderNumber) + } + + /** + * 정기구독 해지 + * @param params 해지 파라미터 + */ + async termination(params: OrderSubscriptionTerminationParams): Promise> { + return this.bootpay.post('order_subscriptions/requests/ing/termination', params) + } +} + +export class OrderSubscriptionModule { + private bootpay: BootpayCommerceResource + public requestIng: OrderSubscriptionRequestIngModule + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + this.requestIng = new OrderSubscriptionRequestIngModule(bootpay) + } + + /** + * 정기구독 목록 조회 + * @param params 조회 파라미터 + */ + async list(params?: OrderSubscriptionListParams): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + if (params.keyword) queryParams.append('keyword', params.keyword) + if (params.s_at) queryParams.append('s_at', params.s_at) + if (params.e_at) queryParams.append('e_at', params.e_at) + if (params.request_type) queryParams.append('request_type', params.request_type) + if (params.user_group_id) queryParams.append('user_group_id', params.user_group_id) + if (params.user_id) queryParams.append('user_id', params.user_id) + } + const query = queryParams.toString() + return this.bootpay.get<{ items: CommerceOrderSubscription[]; total: number }>(`order_subscriptions${query ? `?${query}` : ''}`) + } + + /** + * 정기구독 상세 조회 + * @param orderSubscriptionId 정기구독 ID + */ + async detail(orderSubscriptionId: string): Promise> { + return this.bootpay.get(`order_subscriptions/${orderSubscriptionId}`) + } + + /** + * 정기구독 수정 + * @param params 수정 파라미터 + */ + async update(params: OrderSubscriptionUpdateParams): Promise> { + if (!params.order_subscription_id) { + return Promise.reject({ success: false, error: 'order_subscription_id is required' }) + } + return this.bootpay.put(`order_subscriptions/${params.order_subscription_id}`, params) + } +} diff --git a/src/lib/commerce/modules/order.ts b/src/lib/commerce/modules/order.ts new file mode 100644 index 0000000..ac12243 --- /dev/null +++ b/src/lib/commerce/modules/order.ts @@ -0,0 +1,62 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceOrder, OrderListParams } from '../types' + +export class OrderModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 주문 목록 조회 + * @param params 조회 파라미터 + */ + async list(params?: OrderListParams): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + if (params.keyword) queryParams.append('keyword', params.keyword) + if (params.user_id) queryParams.append('user_id', params.user_id) + if (params.user_group_id) queryParams.append('user_group_id', params.user_group_id) + if (params.cs_type) queryParams.append('cs_type', params.cs_type) + if (params.css_at) queryParams.append('css_at', params.css_at) + if (params.cse_at) queryParams.append('cse_at', params.cse_at) + if (params.subscription_billing_type !== undefined) { + queryParams.append('subscription_billing_type', params.subscription_billing_type.toString()) + } + if (params.status && params.status.length > 0) { + queryParams.append('status', params.status.join(',')) + } + if (params.payment_status && params.payment_status.length > 0) { + queryParams.append('payment_status', params.payment_status.join(',')) + } + if (params.order_subscription_ids && params.order_subscription_ids.length > 0) { + queryParams.append('order_subscription_ids', params.order_subscription_ids.join(',')) + } + } + const query = queryParams.toString() + return this.bootpay.get<{ items: CommerceOrder[]; total: number }>(`orders${query ? `?${query}` : ''}`) + } + + /** + * 주문 상세 조회 + * @param orderId 주문 ID + */ + async detail(orderId: string): Promise> { + return this.bootpay.get(`orders/${orderId}`) + } + + /** + * 월별 주문 조회 + * @param userGroupId 사용자 그룹 ID + * @param searchDate 검색 날짜 (YYYY-MM 형식) + */ + async month(userGroupId: string, searchDate: string): Promise> { + const queryParams = new URLSearchParams() + queryParams.append('user_group_id', userGroupId) + queryParams.append('search_date', searchDate) + return this.bootpay.get(`orders/month?${queryParams.toString()}`) + } +} diff --git a/src/lib/commerce/modules/product.ts b/src/lib/commerce/modules/product.ts new file mode 100644 index 0000000..0415cf6 --- /dev/null +++ b/src/lib/commerce/modules/product.ts @@ -0,0 +1,110 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceProduct, ProductListParams, ProductStatusParams } from '../types' +import FormData from 'form-data' +import fs from 'fs' +import path from 'path' + +export class ProductModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 상품 목록 조회 + * @param params 조회 파라미터 + */ + async list(params?: ProductListParams): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + if (params.keyword) queryParams.append('keyword', params.keyword) + if (params.type !== undefined) queryParams.append('type', params.type.toString()) + if (params.period_type) queryParams.append('period_type', params.period_type) + if (params.s_at) queryParams.append('s_at', params.s_at) + if (params.e_at) queryParams.append('e_at', params.e_at) + if (params.category_code) queryParams.append('category_code', params.category_code) + } + const query = queryParams.toString() + return this.bootpay.get<{ items: CommerceProduct[]; total: number }>(`products${query ? `?${query}` : ''}`) + } + + /** + * 상품 생성 (이미지 포함) + * @param product 상품 정보 + * @param imagePaths 이미지 파일 경로 배열 + */ + async create(product: CommerceProduct, imagePaths?: string[]): Promise> { + const formData = new FormData() + + // 상품 정보를 JSON으로 변환하여 추가 + Object.entries(product).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + if (typeof value === 'object') { + formData.append(key, JSON.stringify(value)) + } else { + formData.append(key, String(value)) + } + } + }) + + // 이미지 파일 추가 + if (imagePaths && imagePaths.length > 0) { + for (const imagePath of imagePaths) { + const fileName = path.basename(imagePath) + formData.append('images', fs.createReadStream(imagePath), fileName) + } + } + + const mode = this.bootpay.commerceConfiguration.mode || 'production' + const url = `${this.bootpay.API_ENTRYPOINTS[mode]}/products` + + return this.bootpay.$http.post(url, formData, { + headers: { + ...formData.getHeaders(), + Authorization: `Bearer ${this.bootpay.getToken()}`, + 'BOOTPAY-ROLE': this.bootpay.getRole() || 'user' + } + }) + } + + /** + * 상품 상세 조회 + * @param productId 상품 ID + */ + async detail(productId: string): Promise> { + return this.bootpay.get(`products/${productId}`) + } + + /** + * 상품 수정 + * @param product 상품 정보 + */ + async update(product: CommerceProduct): Promise> { + if (!product.product_id) { + return Promise.reject({ success: false, error: 'product_id is required' }) + } + return this.bootpay.put(`products/${product.product_id}`, product) + } + + /** + * 상품 상태 변경 + * @param params 상태 변경 파라미터 + */ + async status(params: ProductStatusParams): Promise> { + if (!params.product_id) { + return Promise.reject({ success: false, error: 'product_id is required' }) + } + return this.bootpay.put(`products/${params.product_id}/status`, params) + } + + /** + * 상품 삭제 + * @param productId 상품 ID + */ + async delete(productId: string): Promise> { + return this.bootpay.delete(`products/${productId}`) + } +} diff --git a/src/lib/commerce/modules/user-group.ts b/src/lib/commerce/modules/user-group.ts new file mode 100644 index 0000000..2d05710 --- /dev/null +++ b/src/lib/commerce/modules/user-group.ts @@ -0,0 +1,93 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceUserGroup, UserGroupListParams, UserGroupLimitParams, UserGroupAggregateTransactionParams } from '../types' + +export class UserGroupModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 사용자 그룹 생성 + * @param userGroup 그룹 정보 + */ + async create(userGroup: CommerceUserGroup): Promise> { + return this.bootpay.post('user-groups', userGroup) + } + + /** + * 사용자 그룹 목록 조회 + * @param params 조회 파라미터 + */ + async list(params?: UserGroupListParams): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + if (params.keyword) queryParams.append('keyword', params.keyword) + if (params.corporate_type !== undefined) queryParams.append('corporate_type', params.corporate_type.toString()) + } + const query = queryParams.toString() + return this.bootpay.get<{ items: CommerceUserGroup[]; total: number }>(`user-groups${query ? `?${query}` : ''}`) + } + + /** + * 사용자 그룹 상세 조회 + * @param userGroupId 그룹 ID + */ + async detail(userGroupId: string): Promise> { + return this.bootpay.get(`user-groups/${userGroupId}`) + } + + /** + * 사용자 그룹 수정 + * @param userGroup 그룹 정보 + */ + async update(userGroup: CommerceUserGroup): Promise> { + if (!userGroup.user_group_id) { + return Promise.reject({ success: false, error: 'user_group_id is required' }) + } + return this.bootpay.put(`user-groups/${userGroup.user_group_id}`, userGroup) + } + + /** + * 그룹에 사용자 추가 + * @param userGroupId 그룹 ID + * @param userId 사용자 ID + */ + async userCreate(userGroupId: string, userId: string): Promise> { + return this.bootpay.post(`user-groups/${userGroupId}/add_user`, { user_id: userId }) + } + + /** + * 그룹에서 사용자 제거 + * @param userGroupId 그룹 ID + * @param userId 사용자 ID + */ + async userDelete(userGroupId: string, userId: string): Promise> { + return this.bootpay.delete(`user-groups/${userGroupId}/remove_user?user_id=${userId}`) + } + + /** + * 그룹 제한 설정 + * @param params 제한 설정 파라미터 + */ + async limit(params: UserGroupLimitParams): Promise> { + if (!params.user_group_id) { + return Promise.reject({ success: false, error: 'user_group_id is required' }) + } + return this.bootpay.put(`user-groups/${params.user_group_id}/limit`, params) + } + + /** + * 그룹 거래 집계 조회 + * @param params 집계 파라미터 + */ + async aggregateTransaction(params: UserGroupAggregateTransactionParams): Promise> { + if (!params.user_group_id) { + return Promise.reject({ success: false, error: 'user_group_id is required' }) + } + return this.bootpay.put(`user-groups/${params.user_group_id}/aggregate-transaction`, params) + } +} diff --git a/src/lib/commerce/modules/user.ts b/src/lib/commerce/modules/user.ts new file mode 100644 index 0000000..49867f4 --- /dev/null +++ b/src/lib/commerce/modules/user.ts @@ -0,0 +1,100 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceUser, UserListParams, UserTokenResponse, UserLoginResponse } from '../types' + +export class UserModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 사용자 토큰 발급 + * @param userId 사용자 ID + */ + async token(userId: string): Promise> { + return this.bootpay.post('users/login/token', { user_id: userId }) + } + + /** + * 회원가입 + * @param user 사용자 정보 + */ + async join(user: CommerceUser): Promise> { + return this.bootpay.post('users/join', user) + } + + /** + * 중복 체크 + * @param key 체크할 필드 (login_id, phone, email 등) + * @param value 체크할 값 + */ + async checkExist(key: string, value: string): Promise> { + const encodedValue = encodeURIComponent(value) + return this.bootpay.get<{ exists: boolean }>(`users/join/${key}?pk=${encodedValue}`) + } + + /** + * 본인인증 데이터 조회 + * @param standId 인증 ID + */ + async authenticationData(standId: string): Promise> { + return this.bootpay.get(`users/authenticate/${standId}`) + } + + /** + * 로그인 + * @param loginId 로그인 ID + * @param loginPw 비밀번호 + */ + async login(loginId: string, loginPw: string): Promise> { + return this.bootpay.post('users/login', { + login_id: loginId, + login_pw: loginPw + }) + } + + /** + * 사용자 목록 조회 + * @param params 조회 파라미터 + */ + async list(params?: UserListParams): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + if (params.keyword) queryParams.append('keyword', params.keyword) + if (params.member_type !== undefined) queryParams.append('member_type', params.member_type.toString()) + if (params.type) queryParams.append('type', params.type) + } + const query = queryParams.toString() + return this.bootpay.get<{ items: CommerceUser[]; total: number }>(`users${query ? `?${query}` : ''}`) + } + + /** + * 사용자 상세 조회 + * @param userId 사용자 ID + */ + async detail(userId: string): Promise> { + return this.bootpay.get(`users/${userId}`) + } + + /** + * 사용자 정보 수정 + * @param user 사용자 정보 + */ + async update(user: CommerceUser): Promise> { + if (!user.user_id) { + return Promise.reject({ success: false, error: 'user_id is required' }) + } + return this.bootpay.put(`users/${user.user_id}`, user) + } + + /** + * 사용자 삭제 (회원탈퇴) + * @param userId 사용자 ID + */ + async delete(userId: string): Promise> { + return this.bootpay.delete(`users/${userId}`) + } +} diff --git a/src/lib/commerce/types/common.ts b/src/lib/commerce/types/common.ts new file mode 100644 index 0000000..76bc111 --- /dev/null +++ b/src/lib/commerce/types/common.ts @@ -0,0 +1,23 @@ +// Common Types for Commerce API + +export interface ListParams { + page?: number + limit?: number + keyword?: string +} + +export interface CommerceAddress { + address_id?: string + zipcode?: string + addr1?: string + addr2?: string + phone?: string + name?: string + memo?: string + is_default?: boolean +} + +export interface CommerceAddressInstruction { + instruction_type?: number + instruction?: string +} diff --git a/src/lib/commerce/types/index.ts b/src/lib/commerce/types/index.ts new file mode 100644 index 0000000..2df4da3 --- /dev/null +++ b/src/lib/commerce/types/index.ts @@ -0,0 +1,10 @@ +export * from './common' +export * from './user' +export * from './user-group' +export * from './product' +export * from './invoice' +export * from './order' +export * from './order-cancel' +export * from './order-subscription' +export * from './order-subscription-bill' +export * from './order-subscription-adjustment' diff --git a/src/lib/commerce/types/invoice.ts b/src/lib/commerce/types/invoice.ts new file mode 100644 index 0000000..3134a74 --- /dev/null +++ b/src/lib/commerce/types/invoice.ts @@ -0,0 +1,110 @@ +import { ListParams } from './common' + +// Constants +export const INVOICE_SEND_TYPE_SMS = 1 +export const INVOICE_SEND_TYPE_KAKAO = 2 +export const INVOICE_SEND_TYPE_EMAIL = 3 +export const INVOICE_SEND_TYPE_PUSH = 4 + +export interface CommerceInvoice { + invoice_id?: string + project_id?: string + seller_id?: string + + name?: string + title?: string + memo?: string + product_name?: string + + created_owner_id?: string + created_owner_type?: number + + unit?: number + metadata?: Record + + request_id?: string + sku?: string + + use_redirect?: boolean + redirect_url?: string + + type?: number + parent_id?: string + + subscription_type?: number + subscription_start_at?: string + subscription_end_at?: string + + expired_at?: string + status?: number + deleted?: boolean + + user_collection_type?: number + use_link_redirect?: boolean + + user_id?: string + + send_status?: number + send_types?: number[] + + message_template_id?: string + message_id?: string + message_from?: string + message_type?: number + message_response?: string + + sent_at?: string + pay_at?: string + + price?: number + tax_free_price?: number + + use_editable_username?: boolean + use_editable_phone?: boolean + use_editable_email?: boolean + use_memo?: boolean + + product_ids?: string[] + product_option_ids?: string[] + + tags?: string[] + + password?: string + order_id?: string + uuid?: string + + webhook_url?: string + header_content_type?: number + webhook_retry_count?: number + + product_type?: number + is_open_link?: boolean + + invoice_items?: CommerceInvoiceItem[] + selected_users?: string[] +} + +export interface CommerceInvoiceItem { + invoice_item_id?: string + name?: string + price?: number + qty?: number + tax_free_price?: number +} + +export interface InvoiceListParams extends ListParams {} + +export interface InvoiceCreateParams { + user_id?: string + user_group_id?: string + title: string + name?: string + description?: string + price: number + tax_free_price?: number + expired_at?: string + invoice_items?: CommerceInvoiceItem[] + send_types?: number[] + webhook_url?: string + metadata?: Record +} diff --git a/src/lib/commerce/types/order-cancel.ts b/src/lib/commerce/types/order-cancel.ts new file mode 100644 index 0000000..e55ef19 --- /dev/null +++ b/src/lib/commerce/types/order-cancel.ts @@ -0,0 +1,48 @@ +export interface OrderCancelListParams { + order_id?: string + order_number?: string +} + +export interface CancelProduct { + order_product_id?: string + product_id?: string + qty?: number + cancel_price?: number +} + +export interface CancelOrderSubscriptionBill { + order_subscription_bill_id?: string + cancel_price?: number +} + +export interface RequestCancelParameter { + cancel_products?: CancelProduct[] + cancel_order_subscription_bills?: CancelOrderSubscriptionBill[] + cancel_reason?: string + cancel_type?: number + refund_price?: number +} + +export interface OrderCancelParams { + order_number?: string + request_cancel_parameters?: RequestCancelParameter + is_supervisor?: boolean +} + +export interface OrderCancelActionParams { + order_cancel_request_history_id: string + cancel_reason?: string + refund_price?: number +} + +export interface CommerceOrderCancelRequestHistory { + order_cancel_request_history_id?: string + order_id?: string + order_number?: string + status?: number + cancel_reason?: string + cancel_type?: number + requested_at?: string + processed_at?: string + refund_price?: number +} diff --git a/src/lib/commerce/types/order-subscription-adjustment.ts b/src/lib/commerce/types/order-subscription-adjustment.ts new file mode 100644 index 0000000..3baf678 --- /dev/null +++ b/src/lib/commerce/types/order-subscription-adjustment.ts @@ -0,0 +1,22 @@ +// Constants +export const SUBSCRIPTION_ADJUSTMENT_TYPE_PERIOD_DISCOUNT = 1 + +export interface CommerceOrderSubscriptionAdjustment { + order_subscription_adjustment_id?: string + duration?: number + price?: number + tax_free_price?: number + name?: string + type?: number + created_at?: string +} + +export interface OrderSubscriptionAdjustmentUpdateParams { + order_subscription_id: string + order_subscription_adjustment_id?: string + duration?: number + price?: number + tax_free_price?: number + name?: string + type?: number +} diff --git a/src/lib/commerce/types/order-subscription-bill.ts b/src/lib/commerce/types/order-subscription-bill.ts new file mode 100644 index 0000000..06f6e9b --- /dev/null +++ b/src/lib/commerce/types/order-subscription-bill.ts @@ -0,0 +1,68 @@ +import { ListParams } from './common' + +export interface CommerceOrderSubscriptionBill { + order_subscription_bill_id?: string + order_subscription_id?: string + user_id?: string + user_group_id?: string + + subscription_billing_type?: number + order_name?: string + paid_wallet_id?: string + reserved_wallet_id?: string + + order_number?: string + order_pre_id?: string + order_id?: string + duration?: number + total_subscription_duration?: number + + one_unit_price?: number + one_unit_tax_free_price?: number + setup_price?: number + + price?: number + tax_free_price?: number + unit?: number + + purchase_price?: number + purchase_tax_free_price?: number + + cancelled_price?: number + cancelled_tax_free_price?: number + cancelled_fee?: number + + membership_type?: number + + address_id?: string + user_address?: string + username?: string + user_phone?: string + user_email?: string + user_company_name?: string + user_business_number?: string + + product_ids?: string[] + product_option_ids?: string[] + product_snapshot_ids?: string[] + product_option_snapshot_ids?: string[] + product_type?: number + quantity?: number + + reserve_payment_at?: string + purchased_at?: string + revoked_at?: string + last_error_at?: string + + status?: number + cancel_status?: number + test_code?: string + + service_start_at?: string + service_end_at?: string +} + +export interface OrderSubscriptionBillListParams extends ListParams { + order_subscription_id?: string + status?: number[] +} diff --git a/src/lib/commerce/types/order-subscription.ts b/src/lib/commerce/types/order-subscription.ts new file mode 100644 index 0000000..3ca2994 --- /dev/null +++ b/src/lib/commerce/types/order-subscription.ts @@ -0,0 +1,101 @@ +import { ListParams } from './common' + +export interface CommerceOrderSubscription { + order_subscription_id?: string + seller_id?: string + project_id?: string + order_id?: string + order_pre_id?: string + user_id?: string + user_group_id?: string + wallet_id?: string + + subscription_billing_type?: number + subscription_payment_cycle_type?: number + subscription_payment_date?: number + subscription_billing_base_day?: number + + quantity?: number + is_first_prepaid?: boolean + + one_unit_price?: number + one_unit_tax_free_price?: number + price?: number + tax_free_price?: number + setup_price?: number + + unit?: number + order_name?: string + product_name?: string + option_names?: string[] + + service_start_at?: string + service_end_at?: string + + last_billing_created_at?: string + latest_purchased_at?: string + latest_failed_at?: string + payment_next_at?: string + + current_duration?: number + created_last_duration?: number + payment_last_duration?: number + total_subscription_duration?: number + + membership_type?: number + use_subscription_times?: boolean + + renewal_status?: number + cancel_status?: number + status?: number + cancel_at?: string +} + +export interface OrderSubscriptionListParams extends ListParams { + s_at?: string + e_at?: string + request_type?: string + user_group_id?: string + user_id?: string +} + +export interface OrderSubscriptionUpdateParams { + order_subscription_id: string + next_billing_at?: string + billing_key?: string + status?: number + payment_next_at?: string + service_end_at?: string +} + +// Request Ing Types +export interface OrderSubscriptionPauseParams { + order_subscription_id?: string + order_number?: string + reason?: string + paused_at?: string + expected_resume_at?: string +} + +export interface OrderSubscriptionResumeParams { + order_subscription_id?: string + order_number?: string + resume_at?: string +} + +export interface OrderSubscriptionTerminationParams { + order_subscription_id?: string + order_number?: string + termination_fee?: number + last_bill_refund_price?: number + final_fee?: number + service_end_at?: string + reason?: string +} + +export interface CalcTerminateFeeResponse { + termination_fee?: number + refund_amount?: number + last_bill_refund_price?: number + final_fee?: number +} diff --git a/src/lib/commerce/types/order.ts b/src/lib/commerce/types/order.ts new file mode 100644 index 0000000..a55f258 --- /dev/null +++ b/src/lib/commerce/types/order.ts @@ -0,0 +1,65 @@ +import { ListParams } from './common' + +// Constants +export const SUBSCRIPTION_BILLING_TYPE_NONE = 0 +export const SUBSCRIPTION_BILLING_TYPE_EACH = 1 +export const SUBSCRIPTION_BILLING_TYPE_GROUP = 2 + +export interface CommerceChosenProductOption { + chosen_product_option_id?: string + product_id?: string + product_option_id?: string + product_name?: string + option_name?: string + price?: number + tax_free_price?: number + qty?: number +} + +export interface CommerceOrder { + order_id?: string + order_pre_id?: string + chosen_product_options?: CommerceChosenProductOption[] + + parent_order_id?: string + user_id?: string + seller_id?: string + project_id?: string + status?: number + currency?: number + is_subscription?: boolean + is_leaf?: boolean + total_price?: number + tax_free_price?: number + discount_amount?: number + delivery_price?: number + payment_method?: string + receipt_id?: string + webhook_url?: string + created_at?: string + updated_at?: string + + cancelled_request_history?: CommerceOrderCancellationRequestHistory[] +} + +export interface CommerceOrderCancellationRequestHistory { + order_cancellation_request_history_id?: string + order_id?: string + status?: number + cancel_reason?: string + cancel_type?: number + requested_at?: string + processed_at?: string +} + +export interface OrderListParams extends ListParams { + user_id?: string + user_group_id?: string + status?: number[] + payment_status?: number[] + cs_type?: string + css_at?: string + cse_at?: string + subscription_billing_type?: number + order_subscription_ids?: string[] +} diff --git a/src/lib/commerce/types/product.ts b/src/lib/commerce/types/product.ts new file mode 100644 index 0000000..6bfc85e --- /dev/null +++ b/src/lib/commerce/types/product.ts @@ -0,0 +1,144 @@ +import { ListParams } from './common' + +export interface CommerceProduct { + product_id?: string + category_id?: string + project_id?: string + seller_id?: string + subscription_setting_id?: string + delivery_shipping_id?: string + brand_id?: string + manufacturer_id?: string + + ex_uid?: string + + name?: string + description?: string + images?: string[] + type?: number + tax_type?: number + use_stock?: boolean + stock?: number + use_option_stock?: boolean + use_stock_safe?: boolean + stock_safe?: number + + display_price?: number + tax_free_price?: number + use_discount?: boolean + discount_price?: number + discount_price_type?: number + use_discount_period?: boolean + discount_start_at?: string + discount_end_at?: string + + use_accumulation?: boolean + accumulation_point?: number + accumulation_point_type?: number + + status_display?: boolean + use_display_period?: boolean + display_start_at?: string + display_end_at?: string + status_sale?: boolean + use_sale_period?: boolean + sale_start_at?: string + sale_end_at?: string + + count_sale?: number + count_qna?: number + count_like?: number + count_review?: number + + barcode?: string + sku?: string + search_tags?: string[] + event_tags?: string[] + target_user_tags?: string[] + delivery_tags?: string[] + emotion_tags?: string[] + + use_coupon?: boolean + use_minor?: boolean + use_free_gift?: boolean + free_gift?: string + + use_bulk_purchase_discount?: boolean + bulk_purchase_discount?: Record + + use_review_point?: boolean + review_point?: Record + + use_seo?: boolean + seo_page_title?: string + seo_meta_description?: string + seo_meta_tags?: string[] + + model_id?: string + model_name?: string + manufacturer_name?: string + brand_name?: string + origin_code?: string + origin_name?: string + importer?: string + + used?: boolean + expired_at?: string + manufactured_at?: string + + use_setup_fee?: boolean + setup_fee_value?: number + setup_fee_type?: number + setup_fee_name?: string + setup_fee_text?: string + + use_delivery_shipping?: boolean + delivery_shipping_fee_type?: number + use_overseas_shipping?: boolean + use_delivery_shipping_bundle?: boolean + delivery_shipping_bundle_id?: string + + use_subscription?: boolean + use_subscription_times?: boolean + use_product_price?: boolean + + use_cancel?: boolean + use_able_refund?: boolean + use_able_cart?: boolean + + created_at?: string + updated_at?: string + + options?: CommerceProductOption[] + subscription_setting?: CommerceSubscriptionSetting +} + +export interface CommerceProductOption { + option_id?: string + name?: string + price?: number + stock?: number +} + +export interface CommerceSubscriptionSetting { + subscription_setting_id?: string + period_type?: string + period_value?: number + billing_day?: number + billing_count?: number +} + +export interface ProductListParams extends ListParams { + type?: number + period_type?: string + s_at?: string + e_at?: string + category_code?: string +} + +export interface ProductStatusParams { + product_id: string + status: number + status_display?: boolean + status_sale?: boolean +} diff --git a/src/lib/commerce/types/user-group.ts b/src/lib/commerce/types/user-group.ts new file mode 100644 index 0000000..9caa6be --- /dev/null +++ b/src/lib/commerce/types/user-group.ts @@ -0,0 +1,78 @@ +import { ListParams } from './common' + +export interface CommerceUserGroup { + user_group_id?: string + seller_id?: string + project_id?: string + corporate_type?: number + + bank?: string + bank_code?: string + + count?: number + last_updated_at?: string + status?: number + + phone?: string + email?: string + zipcode?: string + address?: string + address_detail?: string + corporate_extension?: Record + auth_bank?: boolean + + company_name?: string + business_number?: string + registration_number?: string + corporate_established?: string + business_type?: string + business_category?: string + ceo_name?: string + auth_company?: boolean + + manager_name?: string + manager_phone?: string + manager_email?: string + + personal_customs_clearance_code?: string + + point?: number + accumulation?: number + marketing_accept_type?: number + marketing_accept_create_at?: string + marketing_accept_update_at?: string + + use_subscription_aggregate_transaction?: boolean + subscription_month_day?: number + subscription_week_day?: number + + use_limit?: boolean + purchase_limit?: number + subscribed_limit?: number + limit_message?: string + external_uid?: string + is_external?: string +} + +// Constants +export const CORPORATE_TYPE_INDIVIDUAL = 1 +export const CORPORATE_TYPE_CORPORATE = 2 + +export interface UserGroupListParams extends ListParams { + corporate_type?: number +} + +export interface UserGroupLimitParams { + user_group_id: string + use_limit?: boolean + purchase_limit?: number + subscribed_limit?: number + limit_message?: string +} + +export interface UserGroupAggregateTransactionParams { + user_group_id: string + use_subscription_aggregate_transaction?: boolean + subscription_month_day?: number + subscription_week_day?: number +} diff --git a/src/lib/commerce/types/user.ts b/src/lib/commerce/types/user.ts new file mode 100644 index 0000000..99558b6 --- /dev/null +++ b/src/lib/commerce/types/user.ts @@ -0,0 +1,83 @@ +import { ListParams, CommerceAddress } from './common' + +export interface CommerceUserGroupRef { + user_group_id?: string + name?: string +} + +export interface CommerceUser { + user_id?: string + created_at?: string + updated_at?: string + + // 고객 유형 + membership_type?: number + + // 고객 정보 + name?: string + phone?: string + email?: string + tel?: string + nickname?: string + bank_username?: string + bank_account?: string + bank_code?: string + comment?: string + + // 최종상태 + count?: number + status?: number + + // 개인 고객 + gender?: number + birth?: string + individual_extension?: Record + + // 쇼핑몰 회원 + login_id?: string + login_pw?: string + login_type?: number + + group_tags?: string[] + metadata?: Record + + // 인증정보 + auth_sms?: boolean + auth_phone?: boolean + auth_email?: boolean + ci?: string + cd?: string + + join_at?: string + join_confirm_type?: number + lasted_at?: string + + // 약관 동의 + marketing_accept_type?: number + marketing_accept_create_at?: string + marketing_accept_update_at?: string + term_ids?: string[] + + group?: CommerceUserGroupRef + + external_uid?: string + is_external?: string + user_group_id?: string +} + +export interface UserListParams extends ListParams { + member_type?: number + type?: string +} + +export interface UserTokenResponse { + access_token?: string + expired_at?: string + user?: CommerceUser +} + +export interface UserLoginResponse { + access_token?: string + expired_at?: string + user?: CommerceUser +} diff --git a/test/commerce/getAccessToken.js b/test/commerce/getAccessToken.js new file mode 100644 index 0000000..0a9fd3d --- /dev/null +++ b/test/commerce/getAccessToken.js @@ -0,0 +1,22 @@ +// Commerce API - getAccessToken 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' // 'production' | 'development' | 'stage' + }) + + try { + const response = await commerce.getAccessToken() + console.log('Access Token Response:', response) + + // 토큰 확인 + console.log('Has Token:', commerce.hasToken()) + console.log('Current Token:', commerce.getCurrentToken()) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/invoiceCreate.js b/test/commerce/invoiceCreate.js new file mode 100644 index 0000000..9c91b38 --- /dev/null +++ b/test/commerce/invoiceCreate.js @@ -0,0 +1,25 @@ +// Commerce API - Invoice Create (청구서 생성) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.invoice.create({ + user_id: 'USER_ID_HERE', + amount: 50000, + title: '테스트 청구서', + description: '테스트 청구서 설명' + }) + console.log('Invoice Create Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/invoiceDetail.js b/test/commerce/invoiceDetail.js new file mode 100644 index 0000000..5e1af9f --- /dev/null +++ b/test/commerce/invoiceDetail.js @@ -0,0 +1,20 @@ +// Commerce API - Invoice Detail (청구서 상세 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.invoice.detail('INVOICE_ID_HERE') + console.log('Invoice Detail Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/invoiceList.js b/test/commerce/invoiceList.js new file mode 100644 index 0000000..d905419 --- /dev/null +++ b/test/commerce/invoiceList.js @@ -0,0 +1,29 @@ +// Commerce API - Invoice List (청구서 목록 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // 기본 목록 조회 + const response = await commerce.invoice.list() + console.log('Invoice List Response:', response) + + // 파라미터로 조회 + const filteredResponse = await commerce.invoice.list({ + page: 1, + limit: 10, + keyword: '청구서' + }) + console.log('Filtered Invoice List Response:', filteredResponse) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/invoiceNotify.js b/test/commerce/invoiceNotify.js new file mode 100644 index 0000000..79880b8 --- /dev/null +++ b/test/commerce/invoiceNotify.js @@ -0,0 +1,21 @@ +// Commerce API - Invoice Notify (청구서 알림 발송) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // send_types: 1=SMS, 2=Email 등 + const response = await commerce.invoice.notify('INVOICE_ID_HERE', [1, 2]) + console.log('Invoice Notify Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderCancelApprove.js b/test/commerce/orderCancelApprove.js new file mode 100644 index 0000000..4b5d769 --- /dev/null +++ b/test/commerce/orderCancelApprove.js @@ -0,0 +1,23 @@ +// Commerce API - OrderCancel Approve (취소 승인) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderCancel.approve({ + order_cancel_request_history_id: 'ORDER_CANCEL_REQUEST_HISTORY_ID_HERE', + approve_reason: '취소 승인 완료' + }) + console.log('OrderCancel Approve Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderCancelList.js b/test/commerce/orderCancelList.js new file mode 100644 index 0000000..a72550c --- /dev/null +++ b/test/commerce/orderCancelList.js @@ -0,0 +1,33 @@ +// Commerce API - OrderCancel List (취소 요청 목록 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // 기본 목록 조회 + const response = await commerce.orderCancel.list() + console.log('OrderCancel List Response:', response) + + // order_id로 조회 + const byOrderId = await commerce.orderCancel.list({ + order_id: 'ORDER_ID_HERE' + }) + console.log('OrderCancel List by Order ID:', byOrderId) + + // order_number로 조회 + const byOrderNumber = await commerce.orderCancel.list({ + order_number: 'ORDER_NUMBER_HERE' + }) + console.log('OrderCancel List by Order Number:', byOrderNumber) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderCancelReject.js b/test/commerce/orderCancelReject.js new file mode 100644 index 0000000..a19cadd --- /dev/null +++ b/test/commerce/orderCancelReject.js @@ -0,0 +1,23 @@ +// Commerce API - OrderCancel Reject (취소 거절) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderCancel.reject({ + order_cancel_request_history_id: 'ORDER_CANCEL_REQUEST_HISTORY_ID_HERE', + reject_reason: '환불 불가 사유로 인한 거절' + }) + console.log('OrderCancel Reject Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderCancelRequest.js b/test/commerce/orderCancelRequest.js new file mode 100644 index 0000000..f37355b --- /dev/null +++ b/test/commerce/orderCancelRequest.js @@ -0,0 +1,24 @@ +// Commerce API - OrderCancel Request (취소 요청) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderCancel.request({ + order_id: 'ORDER_ID_HERE', + cancel_reason: '고객 요청에 의한 취소', + cancel_amount: 10000 + }) + console.log('OrderCancel Request Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderCancelWithdraw.js b/test/commerce/orderCancelWithdraw.js new file mode 100644 index 0000000..e9477e8 --- /dev/null +++ b/test/commerce/orderCancelWithdraw.js @@ -0,0 +1,20 @@ +// Commerce API - OrderCancel Withdraw (취소 요청 철회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderCancel.withdraw('ORDER_CANCEL_REQUEST_HISTORY_ID_HERE') + console.log('OrderCancel Withdraw Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderDetail.js b/test/commerce/orderDetail.js new file mode 100644 index 0000000..82cf93b --- /dev/null +++ b/test/commerce/orderDetail.js @@ -0,0 +1,20 @@ +// Commerce API - Order Detail (주문 상세 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.order.detail('25112678946009400157') + console.log('Order Detail Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderList.js b/test/commerce/orderList.js new file mode 100644 index 0000000..144f08f --- /dev/null +++ b/test/commerce/orderList.js @@ -0,0 +1,33 @@ +// Commerce API - Order List (주문 목록 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // 기본 목록 조회 + const response = await commerce.order.list() + console.log('Order List Response:', response) + + // 파라미터로 조회 + const filteredResponse = await commerce.order.list({ + page: 1, + limit: 10, + keyword: '주문', + user_id: 'USER_ID_HERE', + user_group_id: 'USER_GROUP_ID_HERE', + status: [1, 2], // 주문 상태 필터 + payment_status: [1] // 결제 상태 필터 + }) + console.log('Filtered Order List Response:', filteredResponse) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderMonth.js b/test/commerce/orderMonth.js new file mode 100644 index 0000000..f3d8ea0 --- /dev/null +++ b/test/commerce/orderMonth.js @@ -0,0 +1,20 @@ +// Commerce API - Order Month (월별 주문 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.order.month('USER_GROUP_ID_HERE', '2024-12') + console.log('Order Month Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionAdjustmentCreate.js b/test/commerce/orderSubscriptionAdjustmentCreate.js new file mode 100644 index 0000000..c6a4020 --- /dev/null +++ b/test/commerce/orderSubscriptionAdjustmentCreate.js @@ -0,0 +1,27 @@ +// Commerce API - OrderSubscriptionAdjustment Create (정기구독 조정 생성) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscriptionAdjustment.create( + 'ORDER_SUBSCRIPTION_ID_HERE', + { + type: 1, // 조정 유형 + amount: 5000, + description: '할인 적용' + } + ) + console.log('OrderSubscriptionAdjustment Create Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionAdjustmentDelete.js b/test/commerce/orderSubscriptionAdjustmentDelete.js new file mode 100644 index 0000000..e7bcda1 --- /dev/null +++ b/test/commerce/orderSubscriptionAdjustmentDelete.js @@ -0,0 +1,23 @@ +// Commerce API - OrderSubscriptionAdjustment Delete (정기구독 조정 삭제) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscriptionAdjustment.delete( + 'ORDER_SUBSCRIPTION_ID_HERE', + 'ORDER_SUBSCRIPTION_ADJUSTMENT_ID_HERE' + ) + console.log('OrderSubscriptionAdjustment Delete Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionAdjustmentUpdate.js b/test/commerce/orderSubscriptionAdjustmentUpdate.js new file mode 100644 index 0000000..6d75ddd --- /dev/null +++ b/test/commerce/orderSubscriptionAdjustmentUpdate.js @@ -0,0 +1,25 @@ +// Commerce API - OrderSubscriptionAdjustment Update (정기구독 조정 수정) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscriptionAdjustment.update({ + order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', + order_subscription_adjustment_id: 'ORDER_SUBSCRIPTION_ADJUSTMENT_ID_HERE', + amount: 3000, + description: '조정 금액 수정' + }) + console.log('OrderSubscriptionAdjustment Update Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionBillDetail.js b/test/commerce/orderSubscriptionBillDetail.js new file mode 100644 index 0000000..0bbd667 --- /dev/null +++ b/test/commerce/orderSubscriptionBillDetail.js @@ -0,0 +1,20 @@ +// Commerce API - OrderSubscriptionBill Detail (정기구독 청구 상세 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscriptionBill.detail('ORDER_SUBSCRIPTION_BILL_ID_HERE') + console.log('OrderSubscriptionBill Detail Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionBillList.js b/test/commerce/orderSubscriptionBillList.js new file mode 100644 index 0000000..f082b8a --- /dev/null +++ b/test/commerce/orderSubscriptionBillList.js @@ -0,0 +1,31 @@ +// Commerce API - OrderSubscriptionBill List (정기구독 청구 목록 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // 기본 목록 조회 + const response = await commerce.orderSubscriptionBill.list() + console.log('OrderSubscriptionBill List Response:', response) + + // 파라미터로 조회 + const filteredResponse = await commerce.orderSubscriptionBill.list({ + page: 1, + limit: 10, + keyword: '청구', + order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', + status: [1, 2] // 청구 상태 필터 + }) + console.log('Filtered OrderSubscriptionBill List Response:', filteredResponse) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionBillUpdate.js b/test/commerce/orderSubscriptionBillUpdate.js new file mode 100644 index 0000000..390b982 --- /dev/null +++ b/test/commerce/orderSubscriptionBillUpdate.js @@ -0,0 +1,24 @@ +// Commerce API - OrderSubscriptionBill Update (정기구독 청구 수정) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscriptionBill.update({ + order_subscription_bill_id: 'ORDER_SUBSCRIPTION_BILL_ID_HERE', + amount: 15000, + billing_date: '2025-02-01' + }) + console.log('OrderSubscriptionBill Update Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionCalculateTerminationFee.js b/test/commerce/orderSubscriptionCalculateTerminationFee.js new file mode 100644 index 0000000..9549946 --- /dev/null +++ b/test/commerce/orderSubscriptionCalculateTerminationFee.js @@ -0,0 +1,29 @@ +// Commerce API - OrderSubscription Calculate Termination Fee (해지 수수료 계산) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // order_subscription_id로 조회 + const response = await commerce.orderSubscription.requestIng.calculateTerminationFee( + 'ORDER_SUBSCRIPTION_ID_HERE' + ) + console.log('Calculate Termination Fee Response:', response) + + // order_number로 조회 + const responseByOrderNumber = await commerce.orderSubscription.requestIng.calculateTerminationFeeByOrderNumber( + 'ORDER_NUMBER_HERE' + ) + console.log('Calculate Termination Fee by Order Number Response:', responseByOrderNumber) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionDetail.js b/test/commerce/orderSubscriptionDetail.js new file mode 100644 index 0000000..08cb6a7 --- /dev/null +++ b/test/commerce/orderSubscriptionDetail.js @@ -0,0 +1,20 @@ +// Commerce API - OrderSubscription Detail (정기구독 상세 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscription.detail('ORDER_SUBSCRIPTION_ID_HERE') + console.log('OrderSubscription Detail Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionList.js b/test/commerce/orderSubscriptionList.js new file mode 100644 index 0000000..3fa3a16 --- /dev/null +++ b/test/commerce/orderSubscriptionList.js @@ -0,0 +1,34 @@ +// Commerce API - OrderSubscription List (정기구독 목록 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // 기본 목록 조회 + const response = await commerce.orderSubscription.list() + console.log('OrderSubscription List Response:', response) + + // 파라미터로 조회 + const filteredResponse = await commerce.orderSubscription.list({ + page: 1, + limit: 10, + keyword: '구독', + user_id: 'USER_ID_HERE', + user_group_id: 'USER_GROUP_ID_HERE', + s_at: '2024-01-01', + e_at: '2024-12-31', + request_type: 'active' + }) + console.log('Filtered OrderSubscription List Response:', filteredResponse) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionPause.js b/test/commerce/orderSubscriptionPause.js new file mode 100644 index 0000000..01da193 --- /dev/null +++ b/test/commerce/orderSubscriptionPause.js @@ -0,0 +1,23 @@ +// Commerce API - OrderSubscription Pause (정기구독 일시정지) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscription.requestIng.pause({ + order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', + pause_reason: '일시 정지 사유' + }) + console.log('OrderSubscription Pause Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionResume.js b/test/commerce/orderSubscriptionResume.js new file mode 100644 index 0000000..1df19c4 --- /dev/null +++ b/test/commerce/orderSubscriptionResume.js @@ -0,0 +1,22 @@ +// Commerce API - OrderSubscription Resume (정기구독 재개) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscription.requestIng.resume({ + order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE' + }) + console.log('OrderSubscription Resume Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionTermination.js b/test/commerce/orderSubscriptionTermination.js new file mode 100644 index 0000000..6c42721 --- /dev/null +++ b/test/commerce/orderSubscriptionTermination.js @@ -0,0 +1,23 @@ +// Commerce API - OrderSubscription Termination (정기구독 해지) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscription.requestIng.termination({ + order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', + termination_reason: '해지 사유' + }) + console.log('OrderSubscription Termination Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionUpdate.js b/test/commerce/orderSubscriptionUpdate.js new file mode 100644 index 0000000..6b96766 --- /dev/null +++ b/test/commerce/orderSubscriptionUpdate.js @@ -0,0 +1,23 @@ +// Commerce API - OrderSubscription Update (정기구독 수정) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.orderSubscription.update({ + order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', + next_billing_date: '2025-01-15' + }) + console.log('OrderSubscription Update Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/productCreate.js b/test/commerce/productCreate.js new file mode 100644 index 0000000..731a171 --- /dev/null +++ b/test/commerce/productCreate.js @@ -0,0 +1,39 @@ +// Commerce API - Product Create (상품 생성) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // 이미지 없이 상품 생성 + const response = await commerce.product.create({ + name: '테스트 상품', + price: 10000, + description: '테스트 상품 설명', + type: 1, // 상품 유형 + status: 1 // 활성 상태 + }) + console.log('Product Create Response:', response) + + // 이미지와 함께 상품 생성 + // const responseWithImages = await commerce.product.create( + // { + // name: '테스트 상품 (이미지 포함)', + // price: 20000, + // description: '테스트 상품 설명', + // type: 1 + // }, + // ['/path/to/image1.jpg', '/path/to/image2.jpg'] + // ) + // console.log('Product Create With Images Response:', responseWithImages) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/productDelete.js b/test/commerce/productDelete.js new file mode 100644 index 0000000..7503dc9 --- /dev/null +++ b/test/commerce/productDelete.js @@ -0,0 +1,20 @@ +// Commerce API - Product Delete (상품 삭제) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.product.delete('PRODUCT_ID_HERE') + console.log('Product Delete Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/productDetail.js b/test/commerce/productDetail.js new file mode 100644 index 0000000..dd8432d --- /dev/null +++ b/test/commerce/productDetail.js @@ -0,0 +1,20 @@ +// Commerce API - Product Detail (상품 상세 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.product.detail('PRODUCT_ID_HERE') + console.log('Product Detail Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/productList.js b/test/commerce/productList.js new file mode 100644 index 0000000..0ec11f9 --- /dev/null +++ b/test/commerce/productList.js @@ -0,0 +1,32 @@ +// Commerce API - Product List (상품 목록 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // 기본 목록 조회 + const response = await commerce.product.list() + console.log('Product List Response:', response) + + // 파라미터로 조회 + const filteredResponse = await commerce.product.list({ + page: 1, + limit: 10, + keyword: '상품', + type: 1, // 상품 유형 + period_type: 'monthly', + category_code: 'CATEGORY_CODE' + }) + console.log('Filtered Product List Response:', filteredResponse) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/productStatus.js b/test/commerce/productStatus.js new file mode 100644 index 0000000..f4445a7 --- /dev/null +++ b/test/commerce/productStatus.js @@ -0,0 +1,23 @@ +// Commerce API - Product Status (상품 상태 변경) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.product.status({ + product_id: 'PRODUCT_ID_HERE', + status: 2 // 비활성 상태로 변경 + }) + console.log('Product Status Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/productUpdate.js b/test/commerce/productUpdate.js new file mode 100644 index 0000000..61816a9 --- /dev/null +++ b/test/commerce/productUpdate.js @@ -0,0 +1,25 @@ +// Commerce API - Product Update (상품 수정) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.product.update({ + product_id: 'PRODUCT_ID_HERE', + name: '수정된 상품명', + price: 15000, + description: '수정된 상품 설명' + }) + console.log('Product Update Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userAuthenticationData.js b/test/commerce/userAuthenticationData.js new file mode 100644 index 0000000..18b2916 --- /dev/null +++ b/test/commerce/userAuthenticationData.js @@ -0,0 +1,20 @@ +// Commerce API - User Authentication Data (본인인증 데이터 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.user.authenticationData('STAND_ID_HERE') + console.log('User Authentication Data Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userCheckExist.js b/test/commerce/userCheckExist.js new file mode 100644 index 0000000..8bc93f8 --- /dev/null +++ b/test/commerce/userCheckExist.js @@ -0,0 +1,29 @@ +// Commerce API - User Check Exist (중복 체크) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // login_id 중복 체크 + const loginIdCheck = await commerce.user.checkExist('login_id', 'test_user@example.com') + console.log('Login ID Exist Check:', loginIdCheck) + + // email 중복 체크 + const emailCheck = await commerce.user.checkExist('email', 'test_user@example.com') + console.log('Email Exist Check:', emailCheck) + + // phone 중복 체크 + const phoneCheck = await commerce.user.checkExist('phone', '010-1234-5678') + console.log('Phone Exist Check:', phoneCheck) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userDelete.js b/test/commerce/userDelete.js new file mode 100644 index 0000000..37dea90 --- /dev/null +++ b/test/commerce/userDelete.js @@ -0,0 +1,20 @@ +// Commerce API - User Delete (회원탈퇴) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.user.delete('USER_ID_HERE') + console.log('User Delete Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userDetail.js b/test/commerce/userDetail.js new file mode 100644 index 0000000..c145124 --- /dev/null +++ b/test/commerce/userDetail.js @@ -0,0 +1,20 @@ +// Commerce API - User Detail (사용자 상세 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.user.detail('USER_ID_HERE') + console.log('User Detail Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userGroupAggregateTransaction.js b/test/commerce/userGroupAggregateTransaction.js new file mode 100644 index 0000000..de5dcdc --- /dev/null +++ b/test/commerce/userGroupAggregateTransaction.js @@ -0,0 +1,24 @@ +// Commerce API - UserGroup Aggregate Transaction (그룹 거래 집계 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.userGroup.aggregateTransaction({ + user_group_id: 'USER_GROUP_ID_HERE', + s_at: '2024-01-01', + e_at: '2024-12-31' + }) + console.log('UserGroup Aggregate Transaction Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userGroupCreate.js b/test/commerce/userGroupCreate.js new file mode 100644 index 0000000..cd4c985 --- /dev/null +++ b/test/commerce/userGroupCreate.js @@ -0,0 +1,24 @@ +// Commerce API - UserGroup Create (사용자 그룹 생성) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.userGroup.create({ + name: '테스트 그룹', + corporate_type: 1, // 법인 유형 + description: '테스트용 사용자 그룹' + }) + console.log('UserGroup Create Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userGroupDetail.js b/test/commerce/userGroupDetail.js new file mode 100644 index 0000000..164783d --- /dev/null +++ b/test/commerce/userGroupDetail.js @@ -0,0 +1,20 @@ +// Commerce API - UserGroup Detail (사용자 그룹 상세 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.userGroup.detail('USER_GROUP_ID_HERE') + console.log('UserGroup Detail Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userGroupLimit.js b/test/commerce/userGroupLimit.js new file mode 100644 index 0000000..c9fed70 --- /dev/null +++ b/test/commerce/userGroupLimit.js @@ -0,0 +1,24 @@ +// Commerce API - UserGroup Limit (그룹 제한 설정) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.userGroup.limit({ + user_group_id: 'USER_GROUP_ID_HERE', + limit_amount: 1000000, // 제한 금액 + limit_count: 100 // 제한 횟수 + }) + console.log('UserGroup Limit Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userGroupList.js b/test/commerce/userGroupList.js new file mode 100644 index 0000000..ca5f046 --- /dev/null +++ b/test/commerce/userGroupList.js @@ -0,0 +1,29 @@ +// Commerce API - UserGroup List (사용자 그룹 목록 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // 기본 목록 조회 + const response = await commerce.userGroup.list() + console.log('UserGroup List Response:', response) + + // 파라미터로 조회 + const filteredResponse = await commerce.userGroup.list({ + page: 1, + limit: 10, + keyword: '테스트' + }) + console.log('Filtered UserGroup List Response:', filteredResponse) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userGroupUpdate.js b/test/commerce/userGroupUpdate.js new file mode 100644 index 0000000..061495b --- /dev/null +++ b/test/commerce/userGroupUpdate.js @@ -0,0 +1,24 @@ +// Commerce API - UserGroup Update (사용자 그룹 수정) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.userGroup.update({ + user_group_id: 'USER_GROUP_ID_HERE', + name: '수정된 그룹명', + description: '수정된 설명' + }) + console.log('UserGroup Update Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userGroupUserCreate.js b/test/commerce/userGroupUserCreate.js new file mode 100644 index 0000000..8aa4580 --- /dev/null +++ b/test/commerce/userGroupUserCreate.js @@ -0,0 +1,23 @@ +// Commerce API - UserGroup User Create (그룹에 사용자 추가) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.userGroup.userCreate( + 'USER_GROUP_ID_HERE', + 'USER_ID_HERE' + ) + console.log('UserGroup User Create Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userGroupUserDelete.js b/test/commerce/userGroupUserDelete.js new file mode 100644 index 0000000..b35a045 --- /dev/null +++ b/test/commerce/userGroupUserDelete.js @@ -0,0 +1,23 @@ +// Commerce API - UserGroup User Delete (그룹에서 사용자 제거) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.userGroup.userDelete( + 'USER_GROUP_ID_HERE', + 'USER_ID_HERE' + ) + console.log('UserGroup User Delete Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userJoin.js b/test/commerce/userJoin.js new file mode 100644 index 0000000..b0217d2 --- /dev/null +++ b/test/commerce/userJoin.js @@ -0,0 +1,26 @@ +// Commerce API - User Join (회원가입) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.user.join({ + login_id: 'test_user@example.com', + login_pw: 'password123', + name: '테스트 사용자', + email: 'test_user@example.com', + phone: '010-1234-5678' + }) + console.log('User Join Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userList.js b/test/commerce/userList.js new file mode 100644 index 0000000..efd9e10 --- /dev/null +++ b/test/commerce/userList.js @@ -0,0 +1,29 @@ +// Commerce API - User List (사용자 목록 조회) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + // 기본 목록 조회 + const response = await commerce.user.list() + console.log('User List Response:', response) + + // 파라미터로 조회 + const filteredResponse = await commerce.user.list({ + page: 1, + limit: 10, + keyword: '테스트' + }) + console.log('Filtered User List Response:', filteredResponse) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userLogin.js b/test/commerce/userLogin.js new file mode 100644 index 0000000..7615000 --- /dev/null +++ b/test/commerce/userLogin.js @@ -0,0 +1,20 @@ +// Commerce API - User Login 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.user.login('test_user@example.com', 'password123') + console.log('User Login Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userToken.js b/test/commerce/userToken.js new file mode 100644 index 0000000..7392550 --- /dev/null +++ b/test/commerce/userToken.js @@ -0,0 +1,20 @@ +// Commerce API - User Token 발급 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.user.token('USER_ID_HERE') + console.log('User Token Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userUpdate.js b/test/commerce/userUpdate.js new file mode 100644 index 0000000..7c49db9 --- /dev/null +++ b/test/commerce/userUpdate.js @@ -0,0 +1,24 @@ +// Commerce API - User Update (사용자 정보 수정) 테스트 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + + const response = await commerce.user.update({ + user_id: 'USER_ID_HERE', + name: '수정된 이름', + phone: '010-9876-5432' + }) + console.log('User Update Response:', response) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/tsconfig.json b/tsconfig.json index 1a28210..b1bf0a6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "DOM" ], "typeRoots": [ - "./src/lib" + "./node_modules/@types" ], "resolveJsonModule": true, "esModuleInterop": true, @@ -29,7 +29,7 @@ } }, "include": [ - "src/*" + "src/**/*" ], "exclude": [ "**/*.spec.ts", From 956e0473b35f965a034ca03b98fa4ea552442a57 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Wed, 26 Nov 2025 17:26:32 +0900 Subject: [PATCH 084/117] example update --- CHNAGELOG.md | 3 +++ package.json | 2 +- test/commerce/getAccessToken.js | 2 +- test/commerce/invoiceCreate.js | 2 +- test/commerce/invoiceDetail.js | 2 +- test/commerce/invoiceList.js | 2 +- test/commerce/invoiceNotify.js | 2 +- test/commerce/orderCancelApprove.js | 2 +- test/commerce/orderCancelList.js | 2 +- test/commerce/orderCancelReject.js | 2 +- test/commerce/orderCancelRequest.js | 2 +- test/commerce/orderCancelWithdraw.js | 2 +- test/commerce/orderDetail.js | 2 +- test/commerce/orderList.js | 2 +- test/commerce/orderMonth.js | 2 +- test/commerce/orderSubscriptionAdjustmentCreate.js | 2 +- test/commerce/orderSubscriptionAdjustmentDelete.js | 2 +- test/commerce/orderSubscriptionAdjustmentUpdate.js | 2 +- test/commerce/orderSubscriptionBillDetail.js | 2 +- test/commerce/orderSubscriptionBillList.js | 2 +- test/commerce/orderSubscriptionBillUpdate.js | 2 +- test/commerce/orderSubscriptionCalculateTerminationFee.js | 2 +- test/commerce/orderSubscriptionDetail.js | 2 +- test/commerce/orderSubscriptionList.js | 2 +- test/commerce/orderSubscriptionPause.js | 2 +- test/commerce/orderSubscriptionResume.js | 2 +- test/commerce/orderSubscriptionTermination.js | 2 +- test/commerce/orderSubscriptionUpdate.js | 2 +- test/commerce/productCreate.js | 2 +- test/commerce/productDelete.js | 2 +- test/commerce/productDetail.js | 2 +- test/commerce/productList.js | 2 +- test/commerce/productStatus.js | 2 +- test/commerce/productUpdate.js | 2 +- test/commerce/userAuthenticationData.js | 2 +- test/commerce/userDelete.js | 2 +- test/commerce/userDetail.js | 2 +- test/commerce/userGroupAggregateTransaction.js | 2 +- test/commerce/userGroupCreate.js | 2 +- test/commerce/userGroupDetail.js | 2 +- test/commerce/userGroupLimit.js | 2 +- test/commerce/userGroupList.js | 2 +- test/commerce/userGroupUpdate.js | 2 +- test/commerce/userGroupUserCreate.js | 2 +- test/commerce/userGroupUserDelete.js | 2 +- test/commerce/userJoin.js | 2 +- test/commerce/userList.js | 2 +- test/commerce/userLogin.js | 2 +- test/commerce/userToken.js | 2 +- test/commerce/userUpdate.js | 2 +- 50 files changed, 52 insertions(+), 49 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 932db00..afb1def 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.4.0 +* Commerce 기능 추가 + ### 2.3.6 * 본인인증 REST API로 요청시 client_ip 파라메터 필수 추가 diff --git a/package.json b/package.json index 69f91c8..f19bcb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.3.6", + "version": "2.4.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", diff --git a/test/commerce/getAccessToken.js b/test/commerce/getAccessToken.js index 0a9fd3d..679a848 100644 --- a/test/commerce/getAccessToken.js +++ b/test/commerce/getAccessToken.js @@ -11,7 +11,7 @@ try { const response = await commerce.getAccessToken() - console.log('Access Token Response:', response) + console.log('Access Token Response:', JSON.stringify(response, null, 2)) // 토큰 확인 console.log('Has Token:', commerce.hasToken()) diff --git a/test/commerce/invoiceCreate.js b/test/commerce/invoiceCreate.js index 9c91b38..2164a59 100644 --- a/test/commerce/invoiceCreate.js +++ b/test/commerce/invoiceCreate.js @@ -18,7 +18,7 @@ title: '테스트 청구서', description: '테스트 청구서 설명' }) - console.log('Invoice Create Response:', response) + console.log('Invoice Create Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/invoiceDetail.js b/test/commerce/invoiceDetail.js index 5e1af9f..cbca8bc 100644 --- a/test/commerce/invoiceDetail.js +++ b/test/commerce/invoiceDetail.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.invoice.detail('INVOICE_ID_HERE') - console.log('Invoice Detail Response:', response) + console.log('Invoice Detail Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/invoiceList.js b/test/commerce/invoiceList.js index d905419..3bd6ead 100644 --- a/test/commerce/invoiceList.js +++ b/test/commerce/invoiceList.js @@ -14,7 +14,7 @@ // 기본 목록 조회 const response = await commerce.invoice.list() - console.log('Invoice List Response:', response) + console.log('Invoice List Response:', JSON.stringify(response, null, 2)) // 파라미터로 조회 const filteredResponse = await commerce.invoice.list({ diff --git a/test/commerce/invoiceNotify.js b/test/commerce/invoiceNotify.js index 79880b8..2452a6b 100644 --- a/test/commerce/invoiceNotify.js +++ b/test/commerce/invoiceNotify.js @@ -14,7 +14,7 @@ // send_types: 1=SMS, 2=Email 등 const response = await commerce.invoice.notify('INVOICE_ID_HERE', [1, 2]) - console.log('Invoice Notify Response:', response) + console.log('Invoice Notify Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderCancelApprove.js b/test/commerce/orderCancelApprove.js index 4b5d769..e18401b 100644 --- a/test/commerce/orderCancelApprove.js +++ b/test/commerce/orderCancelApprove.js @@ -16,7 +16,7 @@ order_cancel_request_history_id: 'ORDER_CANCEL_REQUEST_HISTORY_ID_HERE', approve_reason: '취소 승인 완료' }) - console.log('OrderCancel Approve Response:', response) + console.log('OrderCancel Approve Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderCancelList.js b/test/commerce/orderCancelList.js index a72550c..866afc5 100644 --- a/test/commerce/orderCancelList.js +++ b/test/commerce/orderCancelList.js @@ -14,7 +14,7 @@ // 기본 목록 조회 const response = await commerce.orderCancel.list() - console.log('OrderCancel List Response:', response) + console.log('OrderCancel List Response:', JSON.stringify(response, null, 2)) // order_id로 조회 const byOrderId = await commerce.orderCancel.list({ diff --git a/test/commerce/orderCancelReject.js b/test/commerce/orderCancelReject.js index a19cadd..b07263a 100644 --- a/test/commerce/orderCancelReject.js +++ b/test/commerce/orderCancelReject.js @@ -16,7 +16,7 @@ order_cancel_request_history_id: 'ORDER_CANCEL_REQUEST_HISTORY_ID_HERE', reject_reason: '환불 불가 사유로 인한 거절' }) - console.log('OrderCancel Reject Response:', response) + console.log('OrderCancel Reject Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderCancelRequest.js b/test/commerce/orderCancelRequest.js index f37355b..b1bbe3f 100644 --- a/test/commerce/orderCancelRequest.js +++ b/test/commerce/orderCancelRequest.js @@ -17,7 +17,7 @@ cancel_reason: '고객 요청에 의한 취소', cancel_amount: 10000 }) - console.log('OrderCancel Request Response:', response) + console.log('OrderCancel Request Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderCancelWithdraw.js b/test/commerce/orderCancelWithdraw.js index e9477e8..12d0a39 100644 --- a/test/commerce/orderCancelWithdraw.js +++ b/test/commerce/orderCancelWithdraw.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.orderCancel.withdraw('ORDER_CANCEL_REQUEST_HISTORY_ID_HERE') - console.log('OrderCancel Withdraw Response:', response) + console.log('OrderCancel Withdraw Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderDetail.js b/test/commerce/orderDetail.js index 82cf93b..bfbed0b 100644 --- a/test/commerce/orderDetail.js +++ b/test/commerce/orderDetail.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.order.detail('25112678946009400157') - console.log('Order Detail Response:', response) + console.log('Order Detail Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderList.js b/test/commerce/orderList.js index 144f08f..b212664 100644 --- a/test/commerce/orderList.js +++ b/test/commerce/orderList.js @@ -14,7 +14,7 @@ // 기본 목록 조회 const response = await commerce.order.list() - console.log('Order List Response:', response) + console.log('Order List Response:', JSON.stringify(response, null, 2)) // 파라미터로 조회 const filteredResponse = await commerce.order.list({ diff --git a/test/commerce/orderMonth.js b/test/commerce/orderMonth.js index f3d8ea0..f2c6fd7 100644 --- a/test/commerce/orderMonth.js +++ b/test/commerce/orderMonth.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.order.month('USER_GROUP_ID_HERE', '2024-12') - console.log('Order Month Response:', response) + console.log('Order Month Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionAdjustmentCreate.js b/test/commerce/orderSubscriptionAdjustmentCreate.js index c6a4020..717099e 100644 --- a/test/commerce/orderSubscriptionAdjustmentCreate.js +++ b/test/commerce/orderSubscriptionAdjustmentCreate.js @@ -20,7 +20,7 @@ description: '할인 적용' } ) - console.log('OrderSubscriptionAdjustment Create Response:', response) + console.log('OrderSubscriptionAdjustment Create Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionAdjustmentDelete.js b/test/commerce/orderSubscriptionAdjustmentDelete.js index e7bcda1..2189e38 100644 --- a/test/commerce/orderSubscriptionAdjustmentDelete.js +++ b/test/commerce/orderSubscriptionAdjustmentDelete.js @@ -16,7 +16,7 @@ 'ORDER_SUBSCRIPTION_ID_HERE', 'ORDER_SUBSCRIPTION_ADJUSTMENT_ID_HERE' ) - console.log('OrderSubscriptionAdjustment Delete Response:', response) + console.log('OrderSubscriptionAdjustment Delete Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionAdjustmentUpdate.js b/test/commerce/orderSubscriptionAdjustmentUpdate.js index 6d75ddd..026309c 100644 --- a/test/commerce/orderSubscriptionAdjustmentUpdate.js +++ b/test/commerce/orderSubscriptionAdjustmentUpdate.js @@ -18,7 +18,7 @@ amount: 3000, description: '조정 금액 수정' }) - console.log('OrderSubscriptionAdjustment Update Response:', response) + console.log('OrderSubscriptionAdjustment Update Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionBillDetail.js b/test/commerce/orderSubscriptionBillDetail.js index 0bbd667..4d47d80 100644 --- a/test/commerce/orderSubscriptionBillDetail.js +++ b/test/commerce/orderSubscriptionBillDetail.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.orderSubscriptionBill.detail('ORDER_SUBSCRIPTION_BILL_ID_HERE') - console.log('OrderSubscriptionBill Detail Response:', response) + console.log('OrderSubscriptionBill Detail Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionBillList.js b/test/commerce/orderSubscriptionBillList.js index f082b8a..f955d6a 100644 --- a/test/commerce/orderSubscriptionBillList.js +++ b/test/commerce/orderSubscriptionBillList.js @@ -14,7 +14,7 @@ // 기본 목록 조회 const response = await commerce.orderSubscriptionBill.list() - console.log('OrderSubscriptionBill List Response:', response) + console.log('OrderSubscriptionBill List Response:', JSON.stringify(response, null, 2)) // 파라미터로 조회 const filteredResponse = await commerce.orderSubscriptionBill.list({ diff --git a/test/commerce/orderSubscriptionBillUpdate.js b/test/commerce/orderSubscriptionBillUpdate.js index 390b982..d6352b4 100644 --- a/test/commerce/orderSubscriptionBillUpdate.js +++ b/test/commerce/orderSubscriptionBillUpdate.js @@ -17,7 +17,7 @@ amount: 15000, billing_date: '2025-02-01' }) - console.log('OrderSubscriptionBill Update Response:', response) + console.log('OrderSubscriptionBill Update Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionCalculateTerminationFee.js b/test/commerce/orderSubscriptionCalculateTerminationFee.js index 9549946..7c52890 100644 --- a/test/commerce/orderSubscriptionCalculateTerminationFee.js +++ b/test/commerce/orderSubscriptionCalculateTerminationFee.js @@ -16,7 +16,7 @@ const response = await commerce.orderSubscription.requestIng.calculateTerminationFee( 'ORDER_SUBSCRIPTION_ID_HERE' ) - console.log('Calculate Termination Fee Response:', response) + console.log('Calculate Termination Fee Response:', JSON.stringify(response, null, 2)) // order_number로 조회 const responseByOrderNumber = await commerce.orderSubscription.requestIng.calculateTerminationFeeByOrderNumber( diff --git a/test/commerce/orderSubscriptionDetail.js b/test/commerce/orderSubscriptionDetail.js index 08cb6a7..67ddc77 100644 --- a/test/commerce/orderSubscriptionDetail.js +++ b/test/commerce/orderSubscriptionDetail.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.orderSubscription.detail('ORDER_SUBSCRIPTION_ID_HERE') - console.log('OrderSubscription Detail Response:', response) + console.log('OrderSubscription Detail Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionList.js b/test/commerce/orderSubscriptionList.js index 3fa3a16..8353338 100644 --- a/test/commerce/orderSubscriptionList.js +++ b/test/commerce/orderSubscriptionList.js @@ -14,7 +14,7 @@ // 기본 목록 조회 const response = await commerce.orderSubscription.list() - console.log('OrderSubscription List Response:', response) + console.log('OrderSubscription List Response:', JSON.stringify(response, null, 2)) // 파라미터로 조회 const filteredResponse = await commerce.orderSubscription.list({ diff --git a/test/commerce/orderSubscriptionPause.js b/test/commerce/orderSubscriptionPause.js index 01da193..044c582 100644 --- a/test/commerce/orderSubscriptionPause.js +++ b/test/commerce/orderSubscriptionPause.js @@ -16,7 +16,7 @@ order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', pause_reason: '일시 정지 사유' }) - console.log('OrderSubscription Pause Response:', response) + console.log('OrderSubscription Pause Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionResume.js b/test/commerce/orderSubscriptionResume.js index 1df19c4..6b370c4 100644 --- a/test/commerce/orderSubscriptionResume.js +++ b/test/commerce/orderSubscriptionResume.js @@ -15,7 +15,7 @@ const response = await commerce.orderSubscription.requestIng.resume({ order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE' }) - console.log('OrderSubscription Resume Response:', response) + console.log('OrderSubscription Resume Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionTermination.js b/test/commerce/orderSubscriptionTermination.js index 6c42721..c213a3a 100644 --- a/test/commerce/orderSubscriptionTermination.js +++ b/test/commerce/orderSubscriptionTermination.js @@ -16,7 +16,7 @@ order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', termination_reason: '해지 사유' }) - console.log('OrderSubscription Termination Response:', response) + console.log('OrderSubscription Termination Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionUpdate.js b/test/commerce/orderSubscriptionUpdate.js index 6b96766..5246b69 100644 --- a/test/commerce/orderSubscriptionUpdate.js +++ b/test/commerce/orderSubscriptionUpdate.js @@ -16,7 +16,7 @@ order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', next_billing_date: '2025-01-15' }) - console.log('OrderSubscription Update Response:', response) + console.log('OrderSubscription Update Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/productCreate.js b/test/commerce/productCreate.js index 731a171..3414d83 100644 --- a/test/commerce/productCreate.js +++ b/test/commerce/productCreate.js @@ -20,7 +20,7 @@ type: 1, // 상품 유형 status: 1 // 활성 상태 }) - console.log('Product Create Response:', response) + console.log('Product Create Response:', JSON.stringify(response, null, 2)) // 이미지와 함께 상품 생성 // const responseWithImages = await commerce.product.create( diff --git a/test/commerce/productDelete.js b/test/commerce/productDelete.js index 7503dc9..e55da33 100644 --- a/test/commerce/productDelete.js +++ b/test/commerce/productDelete.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.product.delete('PRODUCT_ID_HERE') - console.log('Product Delete Response:', response) + console.log('Product Delete Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/productDetail.js b/test/commerce/productDetail.js index dd8432d..a9c61fe 100644 --- a/test/commerce/productDetail.js +++ b/test/commerce/productDetail.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.product.detail('PRODUCT_ID_HERE') - console.log('Product Detail Response:', response) + console.log('Product Detail Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/productList.js b/test/commerce/productList.js index 0ec11f9..8489668 100644 --- a/test/commerce/productList.js +++ b/test/commerce/productList.js @@ -14,7 +14,7 @@ // 기본 목록 조회 const response = await commerce.product.list() - console.log('Product List Response:', response) + console.log('Product List Response:', JSON.stringify(response, null, 2)) // 파라미터로 조회 const filteredResponse = await commerce.product.list({ diff --git a/test/commerce/productStatus.js b/test/commerce/productStatus.js index f4445a7..2dcbf10 100644 --- a/test/commerce/productStatus.js +++ b/test/commerce/productStatus.js @@ -16,7 +16,7 @@ product_id: 'PRODUCT_ID_HERE', status: 2 // 비활성 상태로 변경 }) - console.log('Product Status Response:', response) + console.log('Product Status Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/productUpdate.js b/test/commerce/productUpdate.js index 61816a9..e548ce6 100644 --- a/test/commerce/productUpdate.js +++ b/test/commerce/productUpdate.js @@ -18,7 +18,7 @@ price: 15000, description: '수정된 상품 설명' }) - console.log('Product Update Response:', response) + console.log('Product Update Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userAuthenticationData.js b/test/commerce/userAuthenticationData.js index 18b2916..ae367c4 100644 --- a/test/commerce/userAuthenticationData.js +++ b/test/commerce/userAuthenticationData.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.user.authenticationData('STAND_ID_HERE') - console.log('User Authentication Data Response:', response) + console.log('User Authentication Data Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userDelete.js b/test/commerce/userDelete.js index 37dea90..bfd4843 100644 --- a/test/commerce/userDelete.js +++ b/test/commerce/userDelete.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.user.delete('USER_ID_HERE') - console.log('User Delete Response:', response) + console.log('User Delete Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userDetail.js b/test/commerce/userDetail.js index c145124..98d819c 100644 --- a/test/commerce/userDetail.js +++ b/test/commerce/userDetail.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.user.detail('USER_ID_HERE') - console.log('User Detail Response:', response) + console.log('User Detail Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userGroupAggregateTransaction.js b/test/commerce/userGroupAggregateTransaction.js index de5dcdc..7105344 100644 --- a/test/commerce/userGroupAggregateTransaction.js +++ b/test/commerce/userGroupAggregateTransaction.js @@ -17,7 +17,7 @@ s_at: '2024-01-01', e_at: '2024-12-31' }) - console.log('UserGroup Aggregate Transaction Response:', response) + console.log('UserGroup Aggregate Transaction Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userGroupCreate.js b/test/commerce/userGroupCreate.js index cd4c985..ef0d857 100644 --- a/test/commerce/userGroupCreate.js +++ b/test/commerce/userGroupCreate.js @@ -17,7 +17,7 @@ corporate_type: 1, // 법인 유형 description: '테스트용 사용자 그룹' }) - console.log('UserGroup Create Response:', response) + console.log('UserGroup Create Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userGroupDetail.js b/test/commerce/userGroupDetail.js index 164783d..c338501 100644 --- a/test/commerce/userGroupDetail.js +++ b/test/commerce/userGroupDetail.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.userGroup.detail('USER_GROUP_ID_HERE') - console.log('UserGroup Detail Response:', response) + console.log('UserGroup Detail Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userGroupLimit.js b/test/commerce/userGroupLimit.js index c9fed70..2006d6a 100644 --- a/test/commerce/userGroupLimit.js +++ b/test/commerce/userGroupLimit.js @@ -17,7 +17,7 @@ limit_amount: 1000000, // 제한 금액 limit_count: 100 // 제한 횟수 }) - console.log('UserGroup Limit Response:', response) + console.log('UserGroup Limit Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userGroupList.js b/test/commerce/userGroupList.js index ca5f046..25d8576 100644 --- a/test/commerce/userGroupList.js +++ b/test/commerce/userGroupList.js @@ -14,7 +14,7 @@ // 기본 목록 조회 const response = await commerce.userGroup.list() - console.log('UserGroup List Response:', response) + console.log('UserGroup List Response:', JSON.stringify(response, null, 2)) // 파라미터로 조회 const filteredResponse = await commerce.userGroup.list({ diff --git a/test/commerce/userGroupUpdate.js b/test/commerce/userGroupUpdate.js index 061495b..91b7b15 100644 --- a/test/commerce/userGroupUpdate.js +++ b/test/commerce/userGroupUpdate.js @@ -17,7 +17,7 @@ name: '수정된 그룹명', description: '수정된 설명' }) - console.log('UserGroup Update Response:', response) + console.log('UserGroup Update Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userGroupUserCreate.js b/test/commerce/userGroupUserCreate.js index 8aa4580..d5394d6 100644 --- a/test/commerce/userGroupUserCreate.js +++ b/test/commerce/userGroupUserCreate.js @@ -16,7 +16,7 @@ 'USER_GROUP_ID_HERE', 'USER_ID_HERE' ) - console.log('UserGroup User Create Response:', response) + console.log('UserGroup User Create Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userGroupUserDelete.js b/test/commerce/userGroupUserDelete.js index b35a045..6d472b8 100644 --- a/test/commerce/userGroupUserDelete.js +++ b/test/commerce/userGroupUserDelete.js @@ -16,7 +16,7 @@ 'USER_GROUP_ID_HERE', 'USER_ID_HERE' ) - console.log('UserGroup User Delete Response:', response) + console.log('UserGroup User Delete Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userJoin.js b/test/commerce/userJoin.js index b0217d2..8d027c7 100644 --- a/test/commerce/userJoin.js +++ b/test/commerce/userJoin.js @@ -19,7 +19,7 @@ email: 'test_user@example.com', phone: '010-1234-5678' }) - console.log('User Join Response:', response) + console.log('User Join Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userList.js b/test/commerce/userList.js index efd9e10..4c55018 100644 --- a/test/commerce/userList.js +++ b/test/commerce/userList.js @@ -14,7 +14,7 @@ // 기본 목록 조회 const response = await commerce.user.list() - console.log('User List Response:', response) + console.log('User List Response:', JSON.stringify(response, null, 2)) // 파라미터로 조회 const filteredResponse = await commerce.user.list({ diff --git a/test/commerce/userLogin.js b/test/commerce/userLogin.js index 7615000..e22f038 100644 --- a/test/commerce/userLogin.js +++ b/test/commerce/userLogin.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.user.login('test_user@example.com', 'password123') - console.log('User Login Response:', response) + console.log('User Login Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userToken.js b/test/commerce/userToken.js index 7392550..29716b2 100644 --- a/test/commerce/userToken.js +++ b/test/commerce/userToken.js @@ -13,7 +13,7 @@ await commerce.getAccessToken() const response = await commerce.user.token('USER_ID_HERE') - console.log('User Token Response:', response) + console.log('User Token Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/userUpdate.js b/test/commerce/userUpdate.js index 7c49db9..fa09fff 100644 --- a/test/commerce/userUpdate.js +++ b/test/commerce/userUpdate.js @@ -17,7 +17,7 @@ name: '수정된 이름', phone: '010-9876-5432' }) - console.log('User Update Response:', response) + console.log('User Update Response:', JSON.stringify(response, null, 2)) } catch (e) { console.error('Error:', e) } From 190bb82baffcb5910a056c52b5b5f8cbc0de0e97 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Wed, 26 Nov 2025 19:09:08 +0900 Subject: [PATCH 085/117] =?UTF-8?q?*=20Commerce=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 +++ package.json | 2 +- src/bootpay-commerce.ts | 8 ++++---- src/lib/commerce-resource.ts | 19 +++---------------- test/commerce/userCheckExist.js | 6 +++--- test/receiptPayment.js | 4 ++-- 6 files changed, 16 insertions(+), 26 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index afb1def..e4a2280 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.4.1 +* Commerce 응답포맷 개선 + ### 2.4.0 * Commerce 기능 추가 diff --git a/package.json b/package.json index f19bcb3..349a5b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.4.0", + "version": "2.4.1", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", diff --git a/src/bootpay-commerce.ts b/src/bootpay-commerce.ts index d44d10b..2693a64 100644 --- a/src/bootpay-commerce.ts +++ b/src/bootpay-commerce.ts @@ -49,17 +49,17 @@ export class BootpayCommerce extends BootpayCommerceResource { * 액세스 토큰 발급 * client_key/secret_key로 인증 */ - async getAccessToken(): Promise> { + async getAccessToken(): Promise { try { const { client_key, secret_key } = this.commerceConfiguration - const response = await this.postWithBasicAuth('request/token', { + const response: any = await this.postWithBasicAuth('request/token', { client_key, secret_key }) - if (response.success && response.data?.access_token) { - this.setToken(response.data.access_token) + if (response?.access_token) { + this.setToken(response.access_token) } return response diff --git a/src/lib/commerce-resource.ts b/src/lib/commerce-resource.ts index 8aa0dc9..bf9701d 100644 --- a/src/lib/commerce-resource.ts +++ b/src/lib/commerce-resource.ts @@ -54,28 +54,15 @@ export class BootpayCommerceResource { this.$http.interceptors.response.use( (response: AxiosResponse): any => { - const result: BootpayCommerceResponse = { - http_status: response.status, - success: response.status >= 200 && response.status < 300, - data: response.data - } - return result + return response.data }, (error: any) => { if (error.response !== undefined) { - return Promise.reject({ - http_status: error.response.status, - success: false, - data: error.response.data, - error: error.response.data?.message || error.message - } as BootpayCommerceResponse) + return Promise.reject(error.response.data) } else { return Promise.reject({ - http_status: -100, - success: false, - data: null, error: `Request Rest Api Failed to Bootpay Commerce Server, ${error.message}` - } as BootpayCommerceResponse) + }) } } ) diff --git a/test/commerce/userCheckExist.js b/test/commerce/userCheckExist.js index 8bc93f8..8f05188 100644 --- a/test/commerce/userCheckExist.js +++ b/test/commerce/userCheckExist.js @@ -14,15 +14,15 @@ // login_id 중복 체크 const loginIdCheck = await commerce.user.checkExist('login_id', 'test_user@example.com') - console.log('Login ID Exist Check:', loginIdCheck) + console.log('Login ID Exist Check:', JSON.stringify(loginIdCheck, null, 2)) // email 중복 체크 const emailCheck = await commerce.user.checkExist('email', 'test_user@example.com') - console.log('Email Exist Check:', emailCheck) + console.log('Email Exist Check:', JSON.stringify(emailCheck, null, 2)) // phone 중복 체크 const phoneCheck = await commerce.user.checkExist('phone', '010-1234-5678') - console.log('Phone Exist Check:', phoneCheck) + console.log('Phone Exist Check:', JSON.stringify(phoneCheck, null, 2)) } catch (e) { console.error('Error:', e) } diff --git a/test/receiptPayment.js b/test/receiptPayment.js index 972c05c..fb1f6ea 100644 --- a/test/receiptPayment.js +++ b/test/receiptPayment.js @@ -1,7 +1,7 @@ -import { Bootpay } from "./" +// import { Bootpay } from "./" (async () => { - // const Bootpay = require('../dist/bootpay.js').Bootpay + const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ application_id: '5b8f6a4d396fa665fdc2b5ea', private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' From 11065c0dcbbfe59c9bff8f3402d46e354c76a5fc Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Wed, 3 Dec 2025 13:14:57 +0900 Subject: [PATCH 086/117] test code update --- README.md | 139 ++++++++++++++++++++++++- src/lib/commerce-resource.ts | 1 - test/cancelSubscribeReserve.js | 4 +- test/certificate.js | 4 +- test/config.js | 65 ++++++++++++ test/getAccessToken.js | 4 +- test/pg/cancelPayment.js | 22 ++++ test/pg/cancelSubscribeReserve.js | 30 ++++++ test/pg/cashReceiptPublishOnReceipt.js | 24 +++++ test/pg/certificate.js | 17 +++ test/pg/confirmPayment.js | 17 +++ test/pg/destroySubscribeBillingKey.js | 17 +++ test/pg/getAccessToken.js | 16 +++ test/pg/lookupBilling.js | 17 +++ test/pg/lookupSubscribeBilling.js | 17 +++ test/pg/receiptPayment.js | 17 +++ test/pg/requestUserToken.js | 19 ++++ test/pg/shippingStart.js | 27 +++++ test/pg/subscribeCardPayment.js | 22 ++++ test/pg/subscribePaymentReserve.js | 23 ++++ test/requestCashReceipt.js | 4 +- test/shippingStart.js | 4 +- test/test.md | 106 +++++++++++++++++++ 23 files changed, 603 insertions(+), 13 deletions(-) create mode 100644 test/config.js create mode 100644 test/pg/cancelPayment.js create mode 100644 test/pg/cancelSubscribeReserve.js create mode 100644 test/pg/cashReceiptPublishOnReceipt.js create mode 100644 test/pg/certificate.js create mode 100644 test/pg/confirmPayment.js create mode 100644 test/pg/destroySubscribeBillingKey.js create mode 100644 test/pg/getAccessToken.js create mode 100644 test/pg/lookupBilling.js create mode 100644 test/pg/lookupSubscribeBilling.js create mode 100644 test/pg/receiptPayment.js create mode 100644 test/pg/requestUserToken.js create mode 100644 test/pg/shippingStart.js create mode 100644 test/pg/subscribeCardPayment.js create mode 100644 test/pg/subscribePaymentReserve.js create mode 100644 test/test.md diff --git a/README.md b/README.md index 1678f14..fc8b2d7 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 * PG 결제창 연동은 클라이언트 라이브러리에서 수행됩니다. (Javascript, Android, iOS, React Native, Flutter 등) * 결제 검증 및 취소, 빌링키 발급, 본인인증 등의 수행은 서버사이드에서 진행됩니다. (Java, PHP, Python, Ruby, Node.js, Go, ASP.NET 등) -## 목차 -- [사용하기](#사용하기) +## 목차 +- [PG API 사용하기](#사용하기) - [1. 토큰 발급](#1-토큰-발급) - [2. 결제 단건 조회](#2-결제-단건-조회) - [3. 결제 취소 (전액 취소 / 부분 취소)](#3-결제-취소-전액-취소--부분-취소) @@ -31,6 +31,13 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 - [9-2. 현금영수증 발행 취소](#9-2-현금영수증-발행-취소) - [9-3. 별건 현금영수증 발행](#9-3-별건-현금영수증-발행) - [9-4. 별건 현금영수증 발행 취소](#9-4-별건-현금영수증-발행-취소) +- [Commerce API 사용하기](#10-commerce-api) + - [10-1. Commerce API 초기화](#10-1-commerce-api-초기화) + - [10-2. 사용자 관리](#10-2-사용자-관리) + - [10-3. 상품 관리](#10-3-상품-관리) + - [10-4. 주문 관리](#10-4-주문-관리) + - [10-5. 정기구독 관리](#10-5-정기구독-관리) + - [10-6. 청구서 관리](#10-6-청구서-관리) - [Example 프로젝트](#example-프로젝트) - [Documentation](#documentation) - [기술문의](#기술문의) @@ -450,6 +457,134 @@ PG사로 배송정보( 이니시스, KCP만 지원 )를 보내서 에스크로 })() ``` +## 10. Commerce API + +부트페이 Commerce API를 사용하여 사용자, 상품, 주문, 정기구독 등을 관리할 수 있습니다. + +### 10-1. Commerce API 초기화 + +```javascript +const { BootpayCommerce } = require('@bootpay/backend-js') + +const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' // 'production' | 'development' | 'stage' +}) + +// 토큰 발급 +await commerce.getAccessToken() +``` + +### 10-2. 사용자 관리 + +```javascript +// 사용자 목록 조회 +const users = await commerce.user.list({ page: 1, limit: 10 }) + +// 사용자 상세 조회 +const user = await commerce.user.detail('USER_ID') + +// 회원가입 +const newUser = await commerce.user.join({ + login_id: 'test@example.com', + login_pw: 'password123', + name: '홍길동', + email: 'test@example.com', + phone: '010-1234-5678' +}) + +// 사용자 정보 수정 +const updatedUser = await commerce.user.update({ + user_id: 'USER_ID', + name: '수정된 이름' +}) +``` + +### 10-3. 상품 관리 + +```javascript +// 상품 목록 조회 +const products = await commerce.product.list({ page: 1, limit: 10 }) + +// 상품 생성 +const product = await commerce.product.create({ + name: '테스트 상품', + price: 10000, + description: '상품 설명' +}) + +// 상품 상세 조회 +const productDetail = await commerce.product.detail('PRODUCT_ID') + +// 상품 수정 +const updatedProduct = await commerce.product.update({ + product_id: 'PRODUCT_ID', + name: '수정된 상품명', + price: 15000 +}) +``` + +### 10-4. 주문 관리 + +```javascript +// 주문 목록 조회 +const orders = await commerce.order.list({ page: 1, limit: 10 }) + +// 주문 상세 조회 +const order = await commerce.order.detail('ORDER_ID') + +// 월별 주문 조회 +const monthOrders = await commerce.order.month('USER_GROUP_ID', '2024-12') +``` + +### 10-5. 정기구독 관리 + +```javascript +// 정기구독 목록 조회 +const subscriptions = await commerce.orderSubscription.list() + +// 정기구독 상세 조회 +const subscription = await commerce.orderSubscription.detail('ORDER_SUBSCRIPTION_ID') + +// 정기구독 일시정지 +await commerce.orderSubscription.pause({ + order_subscription_id: 'ORDER_SUBSCRIPTION_ID', + pause_days: 30, + reason: '일시정지 사유' +}) + +// 정기구독 재개 +await commerce.orderSubscription.resume({ + order_subscription_id: 'ORDER_SUBSCRIPTION_ID' +}) + +// 정기구독 해지 +await commerce.orderSubscription.termination({ + order_subscription_id: 'ORDER_SUBSCRIPTION_ID', + reason: '해지 사유' +}) +``` + +### 10-6. 청구서 관리 + +```javascript +// 청구서 목록 조회 +const invoices = await commerce.invoice.list() + +// 청구서 생성 +const invoice = await commerce.invoice.create({ + user_id: 'USER_ID', + amount: 50000, + title: '청구서 제목' +}) + +// 청구서 알림 전송 +await commerce.invoice.notify('INVOICE_ID', [1, 2]) // 1: SMS, 2: Email +``` + +더 자세한 Commerce API 사용 예제는 [test/commerce](./test/commerce) 디렉토리를 참고해주세요. + ## Example 프로젝트 [적용한 샘플 프로젝트](https://github.com/bootpay/backend-python-example)을 참조해주세요 diff --git a/src/lib/commerce-resource.ts b/src/lib/commerce-resource.ts index bf9701d..9778a8f 100644 --- a/src/lib/commerce-resource.ts +++ b/src/lib/commerce-resource.ts @@ -18,7 +18,6 @@ export interface CommerceConfiguration { } export interface BootpayCommerceResponse { - http_status: number success: boolean data: T error?: string diff --git a/test/cancelSubscribeReserve.js b/test/cancelSubscribeReserve.js index 682686e..dd3cf0e 100644 --- a/test/cancelSubscribeReserve.js +++ b/test/cancelSubscribeReserve.js @@ -1,8 +1,8 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { // console.log(new Date((new Date()).getTime() + 5000)) diff --git a/test/certificate.js b/test/certificate.js index f4ba74f..0503ce5 100644 --- a/test/certificate.js +++ b/test/certificate.js @@ -1,8 +1,8 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { await Bootpay.getAccessToken() diff --git a/test/config.js b/test/config.js new file mode 100644 index 0000000..72085d9 --- /dev/null +++ b/test/config.js @@ -0,0 +1,65 @@ +/** + * SDK 테스트용 설정 파일 + */ + +// 현재 환경: 'production' 또는 'development' +const CURRENT_ENV = 'production'; + +// PG API 키 +const PG_CREDENTIALS = { + production: { + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + }, + development: { + application_id: '59bfc738e13f337dbd6ca48a', + private_key: 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' + } +}; + +// Commerce API 키 +const COMMERCE_CREDENTIALS = { + production: { + client_key: 'sEN72kYZBiyMNytA8nUGxQ', + secret_key: 'rnZLJamENRgfwTccwmI_Uu9cxsPpAV9X2W-Htg73yfU=' + }, + development: { + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=' + } +}; + +// 테스트 데이터 +const TEST_DATA = { + receipt_id: '628b2206d01c7e00209b6087', + receipt_id_confirm: '62876963d01c7e00209b6028', + receipt_id_cash: '62e0f11f1fc192036b1b3c92', + receipt_id_escrow: '628ae7ffd01c7e001e9b6066', + receipt_id_billing: '62c7ccebcf9f6d001b3adcd4', + receipt_id_transfer: '66541bc4ca4517e69343e24c', + billing_key: '628b2644d01c7e00209b6092', + billing_key_2: '66542dfb4d18d5fc7b43e1b6', + reserve_id: '6490149ca575b40024f0b70d', + reserve_id_2: '628b316cd01c7e00219b6081', + user_id: '1234', + certificate_receipt_id: '61b009aaec81b4057e7f6ecd' +}; + +// PG API 키 가져오기 +function getPgKeys() { + return PG_CREDENTIALS[CURRENT_ENV]; +} + +// Commerce API 키 가져오기 +function getCommerceKeys() { + return COMMERCE_CREDENTIALS[CURRENT_ENV]; +} + +module.exports = { + CURRENT_ENV, + PG_CREDENTIALS, + COMMERCE_CREDENTIALS, + TEST_DATA, + getPgKeys, + getCommerceKeys +}; diff --git a/test/getAccessToken.js b/test/getAccessToken.js index 3f14595..b106c65 100644 --- a/test/getAccessToken.js +++ b/test/getAccessToken.js @@ -4,8 +4,8 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { let response = await Bootpay.getAccessToken() diff --git a/test/pg/cancelPayment.js b/test/pg/cancelPayment.js new file mode 100644 index 0000000..bbca249 --- /dev/null +++ b/test/pg/cancelPayment.js @@ -0,0 +1,22 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.cancelPayment({ + receipt_id: TEST_DATA.receipt_id, + cancel_price: 1000, + cancel_username: '테스트 사용자', + cancel_message: '테스트 취소입니다.' + }); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/cancelSubscribeReserve.js b/test/pg/cancelSubscribeReserve.js new file mode 100644 index 0000000..63ff1ea --- /dev/null +++ b/test/pg/cancelSubscribeReserve.js @@ -0,0 +1,30 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + // 예약 결제 등록 + const reserve = await Bootpay.subscribePaymentReserve({ + billing_key: TEST_DATA.billing_key, + order_name: '테스트결제', + price: 1000, + order_id: Date.now().toString(), + reserve_execute_at: new Date(Date.now() + 60000).toISOString() + }); + console.log('예약 등록:', reserve); + + if (reserve.data && reserve.data.reserve_id) { + // 예약 취소 + const cancel = await Bootpay.cancelSubscribeReserve(reserve.data.reserve_id); + console.log('예약 취소:', cancel); + } + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/cashReceiptPublishOnReceipt.js b/test/pg/cashReceiptPublishOnReceipt.js new file mode 100644 index 0000000..4314c43 --- /dev/null +++ b/test/pg/cashReceiptPublishOnReceipt.js @@ -0,0 +1,24 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.cashReceiptPublishOnReceipt({ + receipt_id: TEST_DATA.receipt_id_cash, + username: '테스트', + email: 'test@bootpay.co.kr', + phone: '01000000000', + identity_no: '01000000000', + cash_receipt_type: '소득공제' + }); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/certificate.js b/test/pg/certificate.js new file mode 100644 index 0000000..2c2e831 --- /dev/null +++ b/test/pg/certificate.js @@ -0,0 +1,17 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.certificate(TEST_DATA.certificate_receipt_id); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/confirmPayment.js b/test/pg/confirmPayment.js new file mode 100644 index 0000000..66b97e3 --- /dev/null +++ b/test/pg/confirmPayment.js @@ -0,0 +1,17 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.confirmPayment(TEST_DATA.receipt_id_confirm); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/destroySubscribeBillingKey.js b/test/pg/destroySubscribeBillingKey.js new file mode 100644 index 0000000..21cad19 --- /dev/null +++ b/test/pg/destroySubscribeBillingKey.js @@ -0,0 +1,17 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.destroySubscribeBillingKey(TEST_DATA.billing_key); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/getAccessToken.js b/test/pg/getAccessToken.js new file mode 100644 index 0000000..e592751 --- /dev/null +++ b/test/pg/getAccessToken.js @@ -0,0 +1,16 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + const response = await Bootpay.getAccessToken(); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/lookupBilling.js b/test/pg/lookupBilling.js new file mode 100644 index 0000000..9fdb69d --- /dev/null +++ b/test/pg/lookupBilling.js @@ -0,0 +1,17 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.lookupBillingKey(TEST_DATA.billing_key_2); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/lookupSubscribeBilling.js b/test/pg/lookupSubscribeBilling.js new file mode 100644 index 0000000..a80bf15 --- /dev/null +++ b/test/pg/lookupSubscribeBilling.js @@ -0,0 +1,17 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.lookupSubscribeBillingKey(TEST_DATA.receipt_id_billing); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/receiptPayment.js b/test/pg/receiptPayment.js new file mode 100644 index 0000000..f57354e --- /dev/null +++ b/test/pg/receiptPayment.js @@ -0,0 +1,17 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.receiptPayment(TEST_DATA.receipt_id); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/requestUserToken.js b/test/pg/requestUserToken.js new file mode 100644 index 0000000..37a5aff --- /dev/null +++ b/test/pg/requestUserToken.js @@ -0,0 +1,19 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.requestUserToken({ + user_id: TEST_DATA.user_id + }); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/shippingStart.js b/test/pg/shippingStart.js new file mode 100644 index 0000000..c291f23 --- /dev/null +++ b/test/pg/shippingStart.js @@ -0,0 +1,27 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.shippingStart({ + receipt_id: TEST_DATA.receipt_id_escrow, + tracking_number: '123456', + delivery_corp: 'CJ대한통운', + user: { + username: '홍길동', + phone: '01000000000', + address: '서울특별시 구로구 디지털로 26길 61', + zipcode: '08882' + } + }); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/subscribeCardPayment.js b/test/pg/subscribeCardPayment.js new file mode 100644 index 0000000..ca620b6 --- /dev/null +++ b/test/pg/subscribeCardPayment.js @@ -0,0 +1,22 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.requestSubscribeCardPayment({ + billing_key: TEST_DATA.billing_key, + order_name: '테스트결제', + price: 1000, + order_id: Date.now().toString() + }); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/subscribePaymentReserve.js b/test/pg/subscribePaymentReserve.js new file mode 100644 index 0000000..b3cd9d8 --- /dev/null +++ b/test/pg/subscribePaymentReserve.js @@ -0,0 +1,23 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getPgKeys, TEST_DATA } = require('../config.js'); + +(async () => { + const keys = getPgKeys(); + Bootpay.setConfiguration({ + application_id: keys.application_id, + private_key: keys.private_key + }); + try { + await Bootpay.getAccessToken(); + const response = await Bootpay.subscribePaymentReserve({ + billing_key: TEST_DATA.billing_key, + order_name: '테스트결제', + price: 1000, + order_id: Date.now().toString(), + reserve_execute_at: new Date(Date.now() + 60000).toISOString() + }); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/requestCashReceipt.js b/test/requestCashReceipt.js index 7390451..b85c13f 100644 --- a/test/requestCashReceipt.js +++ b/test/requestCashReceipt.js @@ -1,8 +1,8 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) // Bootpay.setConfiguration({ // application_id: '59bfc738e13f337dbd6ca48a', diff --git a/test/shippingStart.js b/test/shippingStart.js index 00799ae..216a161 100644 --- a/test/shippingStart.js +++ b/test/shippingStart.js @@ -1,8 +1,8 @@ (async () => { const Bootpay = require('../dist/bootpay.js').Bootpay Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + application_id: '5b8f6a4d396fa665fdc2b5ea', + private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { await Bootpay.getAccessToken() diff --git a/test/test.md b/test/test.md new file mode 100644 index 0000000..d80e266 --- /dev/null +++ b/test/test.md @@ -0,0 +1,106 @@ +# NodeJS SDK 테스트 실행 가이드 + +## 환경 설정 + +`test/config.js` 파일에서 환경을 설정합니다: + +```javascript +// 'production' 또는 'development'로 설정 +const CURRENT_ENV = 'production'; +``` + +## 테스트 실행 + +### 빌드 먼저 실행 +```bash +cd /Users/taesupyoon/bootpay/server/sdk/nodejs +npm run build +``` + +### 개별 테스트 실행 (pg/ 폴더) +```bash +# 토큰 발급 +node test/pg/getAccessToken.js + +# 결제 조회 +node test/pg/receiptPayment.js + +# 결제 승인 +node test/pg/confirmPayment.js + +# 결제 취소 +node test/pg/cancelPayment.js + +# 본인인증 조회 +node test/pg/certificate.js + +# 빌링키 조회 (receipt_id) +node test/pg/lookupSubscribeBilling.js + +# 빌링키 조회 (billing_key) +node test/pg/lookupBilling.js + +# 빌링키 삭제 +node test/pg/destroySubscribeBillingKey.js + +# 카드 정기결제 실행 +node test/pg/subscribeCardPayment.js + +# 예약 결제 +node test/pg/subscribePaymentReserve.js + +# 예약 결제 취소 +node test/pg/cancelSubscribeReserve.js + +# 사용자 토큰 발급 +node test/pg/requestUserToken.js + +# 에스크로 배송시작 +node test/pg/shippingStart.js + +# 결제건 현금영수증 발행 +node test/pg/cashReceiptPublishOnReceipt.js +``` + +### Commerce API 테스트 +```bash +# Commerce 테스트 실행 +node test/commerce/[테스트파일].js +``` + +## 테스트 데이터 + +`test/config.js`에서 `TEST_DATA` 객체를 통해 테스트 데이터를 관리합니다: + +```javascript +const TEST_DATA = { + receipt_id: '628b2206d01c7e00209b6087', + receipt_id_confirm: '62876963d01c7e00209b6028', + receipt_id_cash: '62e0f11f1fc192036b1b3c92', + receipt_id_escrow: '628ae7ffd01c7e001e9b6066', + receipt_id_billing: '62c7ccebcf9f6d001b3adcd4', + receipt_id_transfer: '66541bc4ca4517e69343e24c', + billing_key: '628b2644d01c7e00209b6092', + billing_key_2: '66542dfb4d18d5fc7b43e1b6', + reserve_id: '6490149ca575b40024f0b70d', + reserve_id_2: '628b316cd01c7e00219b6081', + user_id: '1234', + certificate_receipt_id: '61b009aaec81b4057e7f6ecd' +}; +``` + +## 폴더 구조 + +``` +test/ +├── config.js # 환경 설정 및 테스트 데이터 +├── test.md # 테스트 가이드 +├── pg/ # PG API 테스트 (config 사용) +│ ├── getAccessToken.js +│ ├── receiptPayment.js +│ ├── confirmPayment.js +│ └── ... +├── commerce/ # Commerce API 테스트 +│ └── ... +└── [기존파일].js # 기존 테스트 파일 (레거시) +``` From 572bf84c789a3ecabb6c4d297f9bd050cbb6bc80 Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Mon, 23 Feb 2026 17:50:09 +0900 Subject: [PATCH 087/117] feat(store): support supervisor order-subscription actions --- .../commerce/modules/order-subscription.ts | 42 ++++++++++++++++++- src/lib/commerce/types/order-subscription.ts | 27 ++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/lib/commerce/modules/order-subscription.ts b/src/lib/commerce/modules/order-subscription.ts index 9f92e8d..ead3b4f 100644 --- a/src/lib/commerce/modules/order-subscription.ts +++ b/src/lib/commerce/modules/order-subscription.ts @@ -6,7 +6,12 @@ import { OrderSubscriptionPauseParams, OrderSubscriptionResumeParams, OrderSubscriptionTerminationParams, - CalcTerminateFeeResponse + CalcTerminateFeeResponse, + SupervisorOrderSubscriptionApproveParams, + SupervisorOrderSubscriptionRejectParams, + SupervisorOrderSubscriptionTerminateParams, + SupervisorOrderSubscriptionPauseParams, + SupervisorOrderSubscriptionResumeParams } from '../types' export class OrderSubscriptionRequestIngModule { @@ -126,4 +131,39 @@ export class OrderSubscriptionModule { } return this.bootpay.put(`order_subscriptions/${params.order_subscription_id}`, params) } + + async supervisorApprove( + orderSubscriptionId: string, + params: SupervisorOrderSubscriptionApproveParams = {} + ): Promise> { + return this.bootpay.put(`order_subscriptions/${orderSubscriptionId}/approve`, params) + } + + async supervisorReject( + orderSubscriptionId: string, + params: SupervisorOrderSubscriptionRejectParams = {} + ): Promise> { + return this.bootpay.put(`order_subscriptions/${orderSubscriptionId}/reject`, params) + } + + async supervisorTerminate( + orderSubscriptionId: string, + params: SupervisorOrderSubscriptionTerminateParams = {} + ): Promise> { + return this.bootpay.put(`order_subscriptions/${orderSubscriptionId}/terminate`, params) + } + + async supervisorPause( + orderSubscriptionId: string, + params: SupervisorOrderSubscriptionPauseParams + ): Promise> { + return this.bootpay.put(`order_subscriptions/${orderSubscriptionId}/pause`, params) + } + + async supervisorResume( + orderSubscriptionId: string, + params: SupervisorOrderSubscriptionResumeParams = {} + ): Promise> { + return this.bootpay.put(`order_subscriptions/${orderSubscriptionId}/resume`, params) + } } diff --git a/src/lib/commerce/types/order-subscription.ts b/src/lib/commerce/types/order-subscription.ts index 3ca2994..f523d9b 100644 --- a/src/lib/commerce/types/order-subscription.ts +++ b/src/lib/commerce/types/order-subscription.ts @@ -99,3 +99,30 @@ export interface CalcTerminateFeeResponse { last_bill_refund_price?: number final_fee?: number } + +export interface SupervisorOrderSubscriptionApproveParams { + reason?: string +} + +export interface SupervisorOrderSubscriptionRejectParams { + reason?: string +} + +export interface SupervisorOrderSubscriptionTerminateParams { + reason?: string + termination_fee?: number + last_bill_refund_price?: number + final_fee?: number + service_end_at?: string + cancel_date?: string +} + +export interface SupervisorOrderSubscriptionPauseParams { + reason?: string + paused_at: string + expected_resume_at?: string +} + +export interface SupervisorOrderSubscriptionResumeParams { + reason?: string +} From 3a3a0f57cfad18249139b2e6b4fc6fe672c7084b Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Mon, 23 Feb 2026 18:18:26 +0900 Subject: [PATCH 088/117] chore: use basic auth fallback in commerce resource --- src/lib/commerce-resource.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/commerce-resource.ts b/src/lib/commerce-resource.ts index 9778a8f..592740b 100644 --- a/src/lib/commerce-resource.ts +++ b/src/lib/commerce-resource.ts @@ -78,6 +78,11 @@ export class BootpayCommerceResource { if (this.$token !== undefined) { config.headers.set('Authorization', `Bearer ${this.$token}`) + } else { + const basicAuth = this.getBasicAuthHeader() + if (basicAuth) { + config.headers.set('Authorization', basicAuth) + } } return config }, From 40f3b3d42282cdf9422c78b51c9263dd8358c013 Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Mon, 23 Feb 2026 19:16:30 +0900 Subject: [PATCH 089/117] test: add basic-auth product info smoke test code --- tests_basic_auth_product_info.mjs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests_basic_auth_product_info.mjs diff --git a/tests_basic_auth_product_info.mjs b/tests_basic_auth_product_info.mjs new file mode 100644 index 0000000..cc5fd28 --- /dev/null +++ b/tests_basic_auth_product_info.mjs @@ -0,0 +1,21 @@ +import { Buffer } from 'node:buffer'; + +const clientKey = process.env.BP_CLIENT_KEY || 'QIzXk4M3EeD-6B1GTfmGHA'; +const secretKey = process.env.BP_SECRET_KEY || 'vRle44QfyBj7nzJlBbeebqkbtlJVRTS2DQa9Adpz3d8='; +const baseUrl = process.env.BP_BASE_URL || 'https://dev-api.bootapi.com/v1'; + +const basic = Buffer.from(`${clientKey}:${secretKey}`).toString('base64'); +const res = await fetch(`${baseUrl}/products?page=1&limit=1`, { + headers: { + 'Authorization': `Basic ${basic}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'bootpay_api_version': '5.0.0', + 'bootpay_sdk_version': '5.0.0', + 'bootpay_sdk_type': '300' + } +}); + +const body = await res.text(); +console.log(JSON.stringify({ status: res.status, ok: res.ok, preview: body.slice(0, 500) }, null, 2)); +if (!res.ok) process.exit(1); From 8518647d29f7e7e6c4d600f49c75fe7d7f288faa Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Mon, 23 Feb 2026 19:39:31 +0900 Subject: [PATCH 090/117] feat(commerce): add mall aliases and store module parity --- src/bootpay-commerce.ts | 3 +++ src/lib/commerce/modules/index.ts | 2 ++ src/lib/commerce/modules/product.ts | 15 +++++++++++++++ src/lib/commerce/modules/store.ts | 23 +++++++++++++++++++++++ src/lib/commerce/modules/user.ts | 21 +++++++++++++++++++++ 5 files changed, 64 insertions(+) create mode 100644 src/lib/commerce/modules/store.ts diff --git a/src/bootpay-commerce.ts b/src/bootpay-commerce.ts index 2693a64..a075769 100644 --- a/src/bootpay-commerce.ts +++ b/src/bootpay-commerce.ts @@ -8,6 +8,7 @@ import { OrderCancelModule } from './lib/commerce/modules/order-cancel' import { OrderSubscriptionModule } from './lib/commerce/modules/order-subscription' import { OrderSubscriptionBillModule } from './lib/commerce/modules/order-subscription-bill' import { OrderSubscriptionAdjustmentModule } from './lib/commerce/modules/order-subscription-adjustment' +import { StoreModule } from './lib/commerce/modules/store' export interface CommerceTokenResponse { access_token: string @@ -24,6 +25,7 @@ export class BootpayCommerce extends BootpayCommerceResource { public orderSubscription!: OrderSubscriptionModule public orderSubscriptionBill!: OrderSubscriptionBillModule public orderSubscriptionAdjustment!: OrderSubscriptionAdjustmentModule + public store!: StoreModule constructor(configuration?: CommerceConfiguration) { super() @@ -43,6 +45,7 @@ export class BootpayCommerce extends BootpayCommerceResource { this.orderSubscription = new OrderSubscriptionModule(this) this.orderSubscriptionBill = new OrderSubscriptionBillModule(this) this.orderSubscriptionAdjustment = new OrderSubscriptionAdjustmentModule(this) + this.store = new StoreModule(this) } /** diff --git a/src/lib/commerce/modules/index.ts b/src/lib/commerce/modules/index.ts index 3c8cdbd..5832b0d 100644 --- a/src/lib/commerce/modules/index.ts +++ b/src/lib/commerce/modules/index.ts @@ -7,3 +7,5 @@ export * from './order-cancel' export * from './order-subscription' export * from './order-subscription-bill' export * from './order-subscription-adjustment' + +export * from './store' diff --git a/src/lib/commerce/modules/product.ts b/src/lib/commerce/modules/product.ts index 0415cf6..1cccd20 100644 --- a/src/lib/commerce/modules/product.ts +++ b/src/lib/commerce/modules/product.ts @@ -31,6 +31,14 @@ export class ProductModule { return this.bootpay.get<{ items: CommerceProduct[]; total: number }>(`products${query ? `?${query}` : ''}`) } + + /** + * 상품 목록 조회 (Mall API alias) + */ + async products(params?: ProductListParams): Promise> { + return this.list(params) + } + /** * 상품 생성 (이미지 포함) * @param product 상품 정보 @@ -78,6 +86,13 @@ export class ProductModule { return this.bootpay.get(`products/${productId}`) } + /** + * 상품 상세 조회 (Mall API alias) + */ + async productDetail(productId: string): Promise> { + return this.detail(productId) + } + /** * 상품 수정 * @param product 상품 정보 diff --git a/src/lib/commerce/modules/store.ts b/src/lib/commerce/modules/store.ts new file mode 100644 index 0000000..74be956 --- /dev/null +++ b/src/lib/commerce/modules/store.ts @@ -0,0 +1,23 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' + +export class StoreModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 가맹점 기본 정보 조회 (/v1/store) + */ + async info(): Promise> { + return this.bootpay.get('store') + } + + /** + * 가맹점 상세 정보 조회 (/v1/store/detail) + */ + async detail(): Promise> { + return this.bootpay.get('store/detail') + } +} diff --git a/src/lib/commerce/modules/user.ts b/src/lib/commerce/modules/user.ts index 49867f4..ed094f9 100644 --- a/src/lib/commerce/modules/user.ts +++ b/src/lib/commerce/modules/user.ts @@ -54,6 +54,27 @@ export class UserModule { }) } + /** + * 회원 로그인 (Mall API alias) + */ + async userLogin(loginId: string, loginPw: string): Promise> { + return this.login(loginId, loginPw) + } + + /** + * 회원가입 (Mall API alias) + */ + async userJoin(user: CommerceUser): Promise> { + return this.join(user) + } + + /** + * 회원가입 중복 확인 (Mall API alias) + */ + async userJoinCheck(type: string, pk: string): Promise> { + return this.checkExist(type, pk) + } + /** * 사용자 목록 조회 * @param params 조회 파라미터 From 6e4f0f2ae080958e7bcdb53861d83e5e2460736f Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Mon, 23 Feb 2026 19:42:37 +0900 Subject: [PATCH 091/117] fix(build): remove deprecated @types/axios causing TS2688 --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 349a5b7..4ff75eb 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "axios": "^1.7.2" }, "devDependencies": { - "@types/axios": "^0.14.0", "@types/node": "^18.6.2", "ts-node": "^10.7.0", "typescript": "^5.3.3" From c1c4e3855211470b34ebde78ded55365a0765bf3 Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Mon, 23 Feb 2026 19:52:33 +0900 Subject: [PATCH 092/117] chore(store): add get-store naming parity aliases --- src/lib/commerce/modules/store.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/commerce/modules/store.ts b/src/lib/commerce/modules/store.ts index 74be956..71c112c 100644 --- a/src/lib/commerce/modules/store.ts +++ b/src/lib/commerce/modules/store.ts @@ -10,14 +10,22 @@ export class StoreModule { /** * 가맹점 기본 정보 조회 (/v1/store) */ - async info(): Promise> { + async getStore(): Promise> { return this.bootpay.get('store') } + async info(): Promise> { + return this.getStore() + } + /** * 가맹점 상세 정보 조회 (/v1/store/detail) */ - async detail(): Promise> { + async getStoreDetail(): Promise> { return this.bootpay.get('store/detail') } + + async detail(): Promise> { + return this.getStoreDetail() + } } From 25f9cd498192decb6951c161b89b04868b355427 Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Tue, 17 Mar 2026 14:35:54 +0900 Subject: [PATCH 093/117] feat(pg): fallback to basic auth when bearer token is absent --- src/lib/resource.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib/resource.ts b/src/lib/resource.ts index bb7db7b..b1af7fb 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -13,8 +13,10 @@ interface BootpayEntrypoints { } interface BootpayConfiguration { - application_id: string - private_key: string + application_id?: string + private_key?: string + client_key?: string + secret_key?: string mode?: 'development' | 'production' | 'stage' } @@ -36,6 +38,8 @@ export class BootpayBackendNodejsResource { this.bootpayConfiguration = { application_id: '', private_key: '', + client_key: '', + secret_key: '', mode: 'production' } this.API_ENTRYPOINTS = { @@ -65,6 +69,13 @@ export class BootpayBackendNodejsResource { if (config.headers !== undefined) { if (this.$token !== undefined) { config.headers.authorization = `Bearer ${ this.$token }` + } else { + const { client_key, secret_key, application_id, private_key } = this.bootpayConfiguration + const key = client_key || application_id + const secret = secret_key || private_key + if (key && secret) { + config.headers.authorization = `Basic ${Buffer.from(`${key}:${secret}`).toString('base64')}` + } } config.headers['Content-Type'] = 'application/json' config.headers['Accept'] = 'application/json' From 7c628def5b1150823595897a4b30daf0540626b8 Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Tue, 17 Mar 2026 14:41:12 +0900 Subject: [PATCH 094/117] fix(auth): prioritize key type over token in authorization selection --- src/lib/resource.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lib/resource.ts b/src/lib/resource.ts index b1af7fb..ebc0cc3 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -67,14 +67,18 @@ export class BootpayBackendNodejsResource { // @ts-expect-error this.$http.interceptors.request.use((config: AxiosRequestConfig) => { if (config.headers !== undefined) { - if (this.$token !== undefined) { - config.headers.authorization = `Bearer ${ this.$token }` - } else { - const { client_key, secret_key, application_id, private_key } = this.bootpayConfiguration - const key = client_key || application_id - const secret = secret_key || private_key - if (key && secret) { - config.headers.authorization = `Basic ${Buffer.from(`${key}:${secret}`).toString('base64')}` + const { client_key, secret_key, application_id, private_key } = this.bootpayConfiguration + + // 인증 우선순위: + // 1) client_key가 있으면 Basic(client_key:secret_key) + // 2) application_id가 있으면 Bearer(token) 우선, token 미존재 시 Basic(application_id:private_key) fallback + if (client_key && secret_key) { + config.headers.authorization = `Basic ${Buffer.from(`${client_key}:${secret_key}`).toString('base64')}` + } else if (application_id) { + if (this.$token !== undefined) { + config.headers.authorization = `Bearer ${ this.$token }` + } else if (private_key) { + config.headers.authorization = `Basic ${Buffer.from(`${application_id}:${private_key}`).toString('base64')}` } } config.headers['Content-Type'] = 'application/json' From 326dfd41df32a736d6d49a503030c2bd2cfa8f04 Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Tue, 17 Mar 2026 14:48:59 +0900 Subject: [PATCH 095/117] fix(auth): remove app key basic fallback when token missing --- src/lib/resource.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/resource.ts b/src/lib/resource.ts index ebc0cc3..9efeb1c 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -77,8 +77,6 @@ export class BootpayBackendNodejsResource { } else if (application_id) { if (this.$token !== undefined) { config.headers.authorization = `Bearer ${ this.$token }` - } else if (private_key) { - config.headers.authorization = `Basic ${Buffer.from(`${application_id}:${private_key}`).toString('base64')}` } } config.headers['Content-Type'] = 'application/json' From a97275ec9172cfe633e3c294d5c7637685f3e6f7 Mon Sep 17 00:00:00 2001 From: openclaw-bot Date: Tue, 17 Mar 2026 14:51:39 +0900 Subject: [PATCH 096/117] refactor(auth): remove application guard for bearer branch --- src/lib/resource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/resource.ts b/src/lib/resource.ts index 9efeb1c..f034da6 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -74,7 +74,7 @@ export class BootpayBackendNodejsResource { // 2) application_id가 있으면 Bearer(token) 우선, token 미존재 시 Basic(application_id:private_key) fallback if (client_key && secret_key) { config.headers.authorization = `Basic ${Buffer.from(`${client_key}:${secret_key}`).toString('base64')}` - } else if (application_id) { + } else { if (this.$token !== undefined) { config.headers.authorization = `Bearer ${ this.$token }` } From fbe1965113bd358e08fe77fba23befeec08d20ae Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 25 Mar 2026 09:19:46 +0900 Subject: [PATCH 097/117] =?UTF-8?q?2.5.0=20=EB=B0=B0=ED=8F=AC=20=EC=A4=80?= =?UTF-8?q?=EB=B9=84=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ff75eb..4589532 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.4.1", + "version": "2.5.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", From d380ac517799bb618a3d1d19c0e33eef330076dd Mon Sep 17 00:00:00 2001 From: gosomi Date: Wed, 25 Mar 2026 09:34:03 +0900 Subject: [PATCH 098/117] =?UTF-8?q?readme=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHNAGELOG.md | 3 ++ README.md | 103 ++++++++++++++++----------------------------------- 2 files changed, 34 insertions(+), 72 deletions(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index e4a2280..7ec9a0b 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,3 +1,6 @@ +### 2.5.0 +* client_key, secret_key 추가 및 레거시 application_id, private_key 유지 + ### 2.4.1 * Commerce 응답포맷 개선 diff --git a/README.md b/README.md index fc8b2d7..4b69726 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,7 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 ## 목차 - [PG API 사용하기](#사용하기) - - [1. 토큰 발급](#1-토큰-발급) - - [2. 결제 단건 조회](#2-결제-단건-조회) +- [2. 결제 단건 조회](#2-결제-단건-조회) - [3. 결제 취소 (전액 취소 / 부분 취소)](#3-결제-취소-전액-취소--부분-취소) - [4. 자동/빌링/정기 결제](#4-자동빌링정기-결제) - [4-1. 카드 빌링키 발급](#4-1-카드-빌링키-발급) @@ -58,11 +57,10 @@ import { Bootpay } from "@bootpay/backend-js"; (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.cancelPayment({ receipt_id: '628b2206d01c7e00209b6087', cancel_price: 1000, @@ -77,39 +75,16 @@ import { Bootpay } from "@bootpay/backend-js"; ``` -## 1. 토큰 발급 - -부트페이와 서버간 통신을 하기 위해서는 부트페이 서버로부터 토큰을 발급받아야 합니다. -발급된 토큰은 30분간 유효하며, 최초 발급일로부터 30분이 지날 경우 토큰 발급 함수를 재호출 해주셔야 합니다. - -```javascript -(async () => { - Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' - }) - try { - let response = await Bootpay.getAccessToken() - console.log(response) - } catch (e) { - console.log(e) - } -})() - -``` - - ## 2. 결제 단건 조회 결제창 및 정기결제에서 승인/취소된 결제건에 대하여 올바른 결제건인지 서버간 통신으로 결제검증을 합니다. ```javascript (async () => { const Bootpay = require('@bootpay/backend-js').Bootpay Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.receiptPayment('62b12f4b6262500007629fec') console.log(response) } catch (e) { @@ -131,11 +106,10 @@ price를 지정하지 않으면 전액취소 됩니다. ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.cancelPayment({ receipt_id: '628b2206d01c7e00209b6087', cancel_price: 1000, @@ -157,11 +131,10 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribeBillingKey({ pg: '나이스페이', order_name: '테스트결제', @@ -189,11 +162,10 @@ REST API 방식으로 고객의 계좌 정보를 전달하여, PG사에게 빌 ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: '5b8f6a4d396fa665fdc2b5ea', + secret_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribeAutomaticTransferBillingKey({ pg: '나이스페이', order_name: '테스트결제', @@ -221,7 +193,6 @@ REST API 방식으로 고객의 계좌 정보를 전달하여, PG사에게 빌 이후 빌링키 발급 요청시 응답받은 receipt_id로, 출금 동의 확인을 요청합니다. ```javascript try { - await Bootpay.getAccessToken() const response = await Bootpay.publishAutomaticTransferBillingKey('6655069ca691573f1bb9c28a') console.log(response) } catch (e) { @@ -237,11 +208,10 @@ try { ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribeCardPayment({ billing_key: '62b3d166cf9f6d001bd20d59', order_name: '테스트 결제', @@ -260,12 +230,11 @@ try { ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { // console.log(new Date((new Date()).getTime() + 5000)) - await Bootpay.getAccessToken() const response = await Bootpay.subscribePaymentReserve({ billing_key: '62b3d166cf9f6d001bd20d59', order_name: '테스트 결제', @@ -293,12 +262,11 @@ await Bootpay.subscribePaymentReserveLookup(reserve_id) ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { // console.log(new Date((new Date()).getTime() + 5000)) - await Bootpay.getAccessToken() const response = await Bootpay.subscribePaymentReserve({ billing_key: '62b3d166cf9f6d001bd20d59', order_name: '테스트 결제', @@ -321,11 +289,10 @@ await Bootpay.subscribePaymentReserveLookup(reserve_id) ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.destroyBillingKey('62b3d166cf9f6d001bd20d59') console.log(response) } catch (e) { @@ -340,11 +307,10 @@ await Bootpay.subscribePaymentReserveLookup(reserve_id) ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.lookupSubscribeBillingKey('62b3cbbecf9f6d001bd20ce8') console.log(response) } catch (e) { @@ -366,11 +332,10 @@ console.log(response) ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.requestUserToken({ user_id: 'gosomi1', phone:'01012345678' @@ -394,11 +359,10 @@ console.log(response) ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.confirmPayment('62876963d01c7e00209b6028') console.log(response) } catch (e) { @@ -413,11 +377,10 @@ console.log(response) ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.certificate('625783a6cf9f6d001d0aed19') console.log(response) } catch (e) { @@ -434,11 +397,10 @@ PG사로 배송정보( 이니시스, KCP만 지원 )를 보내서 에스크로 ```javascript (async () => { Bootpay.setConfiguration({ - application_id: '59b731f084382614ebf72215', - private_key: 'WwDv0UjfwFa04wYG0LJZZv1xwraQnlhnHE375n52X0U=' + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' }) try { - await Bootpay.getAccessToken() const response = await Bootpay.shippingStart({ receipt_id: "62a9379ad01c7e001f7dc1f3", tracking_number: '123456', @@ -467,13 +429,10 @@ PG사로 배송정보( 이니시스, KCP만 지원 )를 보내서 에스크로 const { BootpayCommerce } = require('@bootpay/backend-js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + client_key: 'r8KT2-2w9iov6IgY93pnuA', + secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=', mode: 'development' // 'production' | 'development' | 'stage' }) - -// 토큰 발급 -await commerce.getAccessToken() ``` ### 10-2. 사용자 관리 From a8657a606f87ad85fd301616f1676fcb260c15bb Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Wed, 29 Apr 2026 15:32:25 +0900 Subject: [PATCH 099/117] =?UTF-8?q?feat(commerce):=20V1=20=EC=8B=A0?= =?UTF-8?q?=EC=84=A4=20=EB=A9=B4=20SDK=20=EB=AA=A8=EB=93=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=E2=80=94=20category=20/=20coupon=20/=20point=20/?= =?UTF-8?q?=20orderSubscriptionRequest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commerce-api V1 외부공개 면에 신설된 4개 도메인을 nodejs SDK 에 통합. - category: list/detail/create/update/destroy (5 액션, GET /v1/categories) - coupon: list/available/preview/download (4 액션, /v1/coupon) - point: balance/transactions/previewUsage/calculateLimit (4 액션, /v1/point) - orderSubscriptionRequest: list/detail/update (3 액션, /v1/order-subscription-requests) → user 모드 (project_id 없음) / supervisor 모드 (project_id 포함) 자동 분기 → 구매자측 요청 생성 (pause/resume/termination 등) 은 기존 commerce.orderSubscription.requestIng.* 사용 BootpayCommerce 인스턴스에 4개 모듈 노출: commerce.category, commerce.coupon, commerce.point, commerce.orderSubscriptionRequest Co-Authored-By: Claude Opus 4.7 (1M context) --- src/bootpay-commerce.ts | 12 +++ src/lib/commerce/modules/category.ts | 46 ++++++++++++ src/lib/commerce/modules/coupon.ts | 45 +++++++++++ src/lib/commerce/modules/index.ts | 4 + .../modules/order-subscription-request.ts | 74 +++++++++++++++++++ src/lib/commerce/modules/point.ts | 63 ++++++++++++++++ src/lib/commerce/types/category.ts | 34 +++++++++ src/lib/commerce/types/coupon.ts | 38 ++++++++++ src/lib/commerce/types/index.ts | 4 + .../types/order-subscription-request.ts | 33 +++++++++ src/lib/commerce/types/point.ts | 63 ++++++++++++++++ 11 files changed, 416 insertions(+) create mode 100644 src/lib/commerce/modules/category.ts create mode 100644 src/lib/commerce/modules/coupon.ts create mode 100644 src/lib/commerce/modules/order-subscription-request.ts create mode 100644 src/lib/commerce/modules/point.ts create mode 100644 src/lib/commerce/types/category.ts create mode 100644 src/lib/commerce/types/coupon.ts create mode 100644 src/lib/commerce/types/order-subscription-request.ts create mode 100644 src/lib/commerce/types/point.ts diff --git a/src/bootpay-commerce.ts b/src/bootpay-commerce.ts index a075769..4d850e6 100644 --- a/src/bootpay-commerce.ts +++ b/src/bootpay-commerce.ts @@ -8,6 +8,10 @@ import { OrderCancelModule } from './lib/commerce/modules/order-cancel' import { OrderSubscriptionModule } from './lib/commerce/modules/order-subscription' import { OrderSubscriptionBillModule } from './lib/commerce/modules/order-subscription-bill' import { OrderSubscriptionAdjustmentModule } from './lib/commerce/modules/order-subscription-adjustment' +import { OrderSubscriptionRequestModule } from './lib/commerce/modules/order-subscription-request' +import { CategoryModule } from './lib/commerce/modules/category' +import { CouponModule } from './lib/commerce/modules/coupon' +import { PointModule } from './lib/commerce/modules/point' import { StoreModule } from './lib/commerce/modules/store' export interface CommerceTokenResponse { @@ -25,6 +29,10 @@ export class BootpayCommerce extends BootpayCommerceResource { public orderSubscription!: OrderSubscriptionModule public orderSubscriptionBill!: OrderSubscriptionBillModule public orderSubscriptionAdjustment!: OrderSubscriptionAdjustmentModule + public orderSubscriptionRequest!: OrderSubscriptionRequestModule + public category!: CategoryModule + public coupon!: CouponModule + public point!: PointModule public store!: StoreModule constructor(configuration?: CommerceConfiguration) { @@ -45,6 +53,10 @@ export class BootpayCommerce extends BootpayCommerceResource { this.orderSubscription = new OrderSubscriptionModule(this) this.orderSubscriptionBill = new OrderSubscriptionBillModule(this) this.orderSubscriptionAdjustment = new OrderSubscriptionAdjustmentModule(this) + this.orderSubscriptionRequest = new OrderSubscriptionRequestModule(this) + this.category = new CategoryModule(this) + this.coupon = new CouponModule(this) + this.point = new PointModule(this) this.store = new StoreModule(this) } diff --git a/src/lib/commerce/modules/category.ts b/src/lib/commerce/modules/category.ts new file mode 100644 index 0000000..cb25efa --- /dev/null +++ b/src/lib/commerce/modules/category.ts @@ -0,0 +1,46 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceCategory, CategoryCreateParams, CategoryUpdateParams } from '../types' + +export class CategoryModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 카테고리 트리 조회 + */ + async list(): Promise> { + return this.bootpay.get('categories') + } + + /** + * 카테고리 단건 조회 + */ + async detail(categoryId: string): Promise> { + return this.bootpay.get(`categories/${categoryId}`) + } + + /** + * 카테고리 생성 + */ + async create(params: CategoryCreateParams): Promise> { + return this.bootpay.post('categories', params) + } + + /** + * 카테고리 수정 + */ + async update(params: CategoryUpdateParams): Promise> { + const { category_id, ...rest } = params + return this.bootpay.put(`categories/${category_id}`, rest) + } + + /** + * 카테고리 삭제 + */ + async destroy(categoryId: string): Promise> { + return this.bootpay.delete(`categories/${categoryId}`) + } +} diff --git a/src/lib/commerce/modules/coupon.ts b/src/lib/commerce/modules/coupon.ts new file mode 100644 index 0000000..2196492 --- /dev/null +++ b/src/lib/commerce/modules/coupon.ts @@ -0,0 +1,45 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceCoupon, CouponListParams, CouponPreviewParams, CouponDownloadParams } from '../types' + +export class CouponModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 사용자 보유 쿠폰 목록 + */ + async list(params?: CouponListParams): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.status) queryParams.append('status', params.status) + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + } + const query = queryParams.toString() + return this.bootpay.get(`coupon${query ? `?${query}` : ''}`) + } + + /** + * 다운로드 가능한 쿠폰 목록 + */ + async available(): Promise> { + return this.bootpay.get('coupon/available') + } + + /** + * 쿠폰 적용 미리보기 + */ + async preview(params: CouponPreviewParams): Promise> { + return this.bootpay.post('coupon/preview', params) + } + + /** + * 쿠폰 다운로드 (issue_from_template) + */ + async download(params: CouponDownloadParams): Promise> { + return this.bootpay.post('coupon/download', params) + } +} diff --git a/src/lib/commerce/modules/index.ts b/src/lib/commerce/modules/index.ts index 5832b0d..59253ce 100644 --- a/src/lib/commerce/modules/index.ts +++ b/src/lib/commerce/modules/index.ts @@ -7,5 +7,9 @@ export * from './order-cancel' export * from './order-subscription' export * from './order-subscription-bill' export * from './order-subscription-adjustment' +export * from './order-subscription-request' +export * from './category' +export * from './coupon' +export * from './point' export * from './store' diff --git a/src/lib/commerce/modules/order-subscription-request.ts b/src/lib/commerce/modules/order-subscription-request.ts new file mode 100644 index 0000000..7794b47 --- /dev/null +++ b/src/lib/commerce/modules/order-subscription-request.ts @@ -0,0 +1,74 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { + OrderSubscriptionRequest, + OrderSubscriptionRequestListParams, + OrderSubscriptionRequestUpdateParams +} from '../types' + +/** + * V1 OrderSubscription Request 조회/승인 모듈 + * + * 본인 모드 (user role): project_id 없이 호출 → 본인 요청 목록/단건 + * 슈퍼바이저 모드 (supervisor role): project_id 포함 → 프로젝트 전체 + update (승인/거절) + * + * 구매자측 요청 생성 (pause/resume/termination 등) 은 + * `commerce.orderSubscription.requestIng.*` 모듈을 사용한다. + */ +export class OrderSubscriptionRequestModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 요청 목록 조회 (user / supervisor 공용) + */ + async list( + params?: OrderSubscriptionRequestListParams + ): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.project_id) queryParams.append('project_id', params.project_id) + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + if (params.request_type !== undefined) queryParams.append('request_type', params.request_type.toString()) + if (params.status !== undefined) queryParams.append('status', params.status.toString()) + if (params.s_at) queryParams.append('s_at', params.s_at) + if (params.e_at) queryParams.append('e_at', params.e_at) + if (params.keyword) queryParams.append('keyword', params.keyword) + } + const query = queryParams.toString() + return this.bootpay.get<{ items: OrderSubscriptionRequest[]; total: number }>( + `order-subscription-requests${query ? `?${query}` : ''}` + ) + } + + /** + * 요청 단건 조회 (user / supervisor 공용) + */ + async detail( + orderSubscriptionRequestHistoryId: string, + projectId?: string + ): Promise> { + const queryParams = new URLSearchParams() + if (projectId) queryParams.append('project_id', projectId) + const query = queryParams.toString() + return this.bootpay.get( + `order-subscription-requests/${orderSubscriptionRequestHistoryId}${query ? `?${query}` : ''}` + ) + } + + /** + * 요청 승인/거절 (supervisor 전용) + */ + async update( + params: OrderSubscriptionRequestUpdateParams + ): Promise> { + const { order_subscription_request_history_id, ...rest } = params + return this.bootpay.put( + `order-subscription-requests/${order_subscription_request_history_id}`, + rest + ) + } +} diff --git a/src/lib/commerce/modules/point.ts b/src/lib/commerce/modules/point.ts new file mode 100644 index 0000000..9ab401d --- /dev/null +++ b/src/lib/commerce/modules/point.ts @@ -0,0 +1,63 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { + PointBalance, + PointTransactionsParams, + PointTransactionsResponse, + PointPreviewUsageParams, + PointPreviewUsageResponse, + PointCalculateLimitParams, + PointCalculateLimitResponse +} from '../types' + +export class PointModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 적립금 잔액 조회 + */ + async balance(): Promise> { + return this.bootpay.get('point/balance') + } + + /** + * 적립금 내역 조회 + */ + async transactions( + params?: PointTransactionsParams + ): Promise> { + const queryParams = new URLSearchParams() + if (params) { + if (params.page !== undefined) queryParams.append('page', params.page.toString()) + if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) + if (params.transaction_type !== undefined) { + queryParams.append('transaction_type', params.transaction_type.toString()) + } + } + const query = queryParams.toString() + return this.bootpay.get( + `point/transactions${query ? `?${query}` : ''}` + ) + } + + /** + * 적립금 사용 미리보기 + */ + async previewUsage( + params: PointPreviewUsageParams + ): Promise> { + return this.bootpay.post('point/preview_usage', params) + } + + /** + * 적립금 사용 한도 계산 + */ + async calculateLimit( + params: PointCalculateLimitParams + ): Promise> { + return this.bootpay.post('point/calculate_limit', params) + } +} diff --git a/src/lib/commerce/types/category.ts b/src/lib/commerce/types/category.ts new file mode 100644 index 0000000..e851390 --- /dev/null +++ b/src/lib/commerce/types/category.ts @@ -0,0 +1,34 @@ +export interface CommerceCategory { + category_id?: string + seller_id?: string + project_id?: string + name?: string + parent_category_id?: string | null + parent_categories?: string[] + status_display?: boolean + status_best?: boolean + filter_color?: number + filter_size?: number + idx?: number + created_at?: string + updated_at?: string +} + +export interface CategoryCreateParams { + name: string + parent_category_id?: string + status_display?: boolean + status_best?: boolean + filter_color?: number + filter_size?: number +} + +export interface CategoryUpdateParams { + category_id: string + name?: string + parent_category_id?: string + status_display?: boolean + status_best?: boolean + filter_color?: number + filter_size?: number +} diff --git a/src/lib/commerce/types/coupon.ts b/src/lib/commerce/types/coupon.ts new file mode 100644 index 0000000..0268a93 --- /dev/null +++ b/src/lib/commerce/types/coupon.ts @@ -0,0 +1,38 @@ +export interface CommerceCoupon { + coupon_id?: string + coupon_template_id?: string + user_id?: string + project_id?: string + name?: string + discount_type?: number + discount_value?: number + min_order_amount?: number + max_discount_amount?: number + status?: number + issued_at?: string + used_at?: string | null + expires_at?: string | null + created_at?: string +} + +export interface CouponListParams { + status?: string + page?: number + limit?: number +} + +export interface CouponPreviewOrderItem { + order_product_id?: string + product_id?: string + qty?: number + price?: number +} + +export interface CouponPreviewParams { + coupon_ids: string[] + order_items: CouponPreviewOrderItem[] +} + +export interface CouponDownloadParams { + coupon_template_id: string +} diff --git a/src/lib/commerce/types/index.ts b/src/lib/commerce/types/index.ts index 2df4da3..e378857 100644 --- a/src/lib/commerce/types/index.ts +++ b/src/lib/commerce/types/index.ts @@ -8,3 +8,7 @@ export * from './order-cancel' export * from './order-subscription' export * from './order-subscription-bill' export * from './order-subscription-adjustment' +export * from './order-subscription-request' +export * from './category' +export * from './coupon' +export * from './point' diff --git a/src/lib/commerce/types/order-subscription-request.ts b/src/lib/commerce/types/order-subscription-request.ts new file mode 100644 index 0000000..c39dde4 --- /dev/null +++ b/src/lib/commerce/types/order-subscription-request.ts @@ -0,0 +1,33 @@ +export interface OrderSubscriptionRequest { + order_subscription_request_history_id?: string + order_subscription_id?: string + project_id?: string + user_id?: string + request_type?: number + status?: number + reason?: string + requested_at?: string + processed_at?: string | null + created_at?: string + updated_at?: string +} + +export interface OrderSubscriptionRequestListParams { + project_id?: string + page?: number + limit?: number + request_type?: number + status?: number + s_at?: string + e_at?: string + keyword?: string +} + +export type OrderSubscriptionRequestApprovalAction = 'approve' | 'reject' + +export interface OrderSubscriptionRequestUpdateParams { + order_subscription_request_history_id: string + approval: OrderSubscriptionRequestApprovalAction + reason?: string + [extra: string]: unknown +} diff --git a/src/lib/commerce/types/point.ts b/src/lib/commerce/types/point.ts new file mode 100644 index 0000000..b23469c --- /dev/null +++ b/src/lib/commerce/types/point.ts @@ -0,0 +1,63 @@ +export interface PointBalance { + available_balance?: number + total_earned?: number + total_used?: number + is_negative?: boolean +} + +export interface PointTransaction { + transaction_id?: string + transaction_type?: number + amount?: number + balance_after?: number + reason?: string + type?: number + order_id?: string | null + review_id?: string | null + earned_at?: string | null + expires_at?: string | null + expired?: boolean + remaining_balance?: number + created_at?: string | null +} + +export interface PointTransactionsResponse { + transactions: PointTransaction[] + total_count: number + page: number + limit: number + total_pages: number +} + +export interface PointTransactionsParams { + page?: number + limit?: number + transaction_type?: number +} + +export interface PointPreviewUsageParams { + amount: number + order_total: number +} + +export interface PointPreviewUsageResponse { + current_balance?: number + use_amount?: number + balance_after?: number + order_total?: number + payment_amount?: number + is_valid?: boolean +} + +export interface PointCalculateLimitParams { + order_total: number +} + +export interface PointCalculateLimitResponse { + max_usable?: number + available_balance?: number + order_total?: number + max_rate?: number | null + min_usage?: number + reason?: string +} From d6d5df3b354195089262d4b4b542b6c558ec7d7d Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Wed, 29 Apr 2026 15:32:36 +0900 Subject: [PATCH 100/117] =?UTF-8?q?test(commerce):=20V1=20=EC=8B=A0?= =?UTF-8?q?=EC=84=A4=20=EB=A9=B4=20dev-api=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=2014=EC=A2=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 신규 4 모듈 (category / coupon / point / orderSubscriptionRequest) 의 모든 메서드를 dev-api 로 호출하는 검증 스크립트. - category: List / Detail / Create / Update / Delete (5) - coupon: List / Available / Preview / Download (4) - point: Balance / Transactions / PreviewUsage / CalculateLimit (4) - orderSubscriptionRequest: List (1) dev 검증 결과: - categoryList: 200 OK [] 정상 응답 (ApiScope 시드 완료) - 나머지: API_ROLE_NOT_SUPPORT — 대상 키의 ApiScope DB 시드는 internal-api 별도 트랙 (apiscope-seed-runbook.md 참조) → SDK 코드/HTTP 플러밍은 모두 정상 Co-Authored-By: Claude Opus 4.7 (1M context) --- test/commerce/categoryCreate.js | 23 ++++++++++++++++++ test/commerce/categoryDelete.js | 19 +++++++++++++++ test/commerce/categoryDetail.js | 19 +++++++++++++++ test/commerce/categoryList.js | 19 +++++++++++++++ test/commerce/categoryUpdate.js | 24 +++++++++++++++++++ test/commerce/couponAvailable.js | 19 +++++++++++++++ test/commerce/couponDownload.js | 21 ++++++++++++++++ test/commerce/couponList.js | 19 +++++++++++++++ test/commerce/couponPreview.js | 22 +++++++++++++++++ test/commerce/orderSubscriptionRequestList.js | 19 +++++++++++++++ test/commerce/pointBalance.js | 19 +++++++++++++++ test/commerce/pointCalculateLimit.js | 19 +++++++++++++++ test/commerce/pointPreviewUsage.js | 22 +++++++++++++++++ test/commerce/pointTransactions.js | 19 +++++++++++++++ 14 files changed, 283 insertions(+) create mode 100644 test/commerce/categoryCreate.js create mode 100644 test/commerce/categoryDelete.js create mode 100644 test/commerce/categoryDetail.js create mode 100644 test/commerce/categoryList.js create mode 100644 test/commerce/categoryUpdate.js create mode 100644 test/commerce/couponAvailable.js create mode 100644 test/commerce/couponDownload.js create mode 100644 test/commerce/couponList.js create mode 100644 test/commerce/couponPreview.js create mode 100644 test/commerce/orderSubscriptionRequestList.js create mode 100644 test/commerce/pointBalance.js create mode 100644 test/commerce/pointCalculateLimit.js create mode 100644 test/commerce/pointPreviewUsage.js create mode 100644 test/commerce/pointTransactions.js diff --git a/test/commerce/categoryCreate.js b/test/commerce/categoryCreate.js new file mode 100644 index 0000000..e4fffc9 --- /dev/null +++ b/test/commerce/categoryCreate.js @@ -0,0 +1,23 @@ +// Commerce API - Category 생성 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.category.create({ + name: 'SDK Test Category', + status_display: true, + status_best: false + }) + console.log('Category Create:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/categoryDelete.js b/test/commerce/categoryDelete.js new file mode 100644 index 0000000..d2dd03f --- /dev/null +++ b/test/commerce/categoryDelete.js @@ -0,0 +1,19 @@ +// Commerce API - Category 삭제 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.category.destroy('CATEGORY_ID_HERE') + console.log('Category Delete:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/categoryDetail.js b/test/commerce/categoryDetail.js new file mode 100644 index 0000000..b2dea37 --- /dev/null +++ b/test/commerce/categoryDetail.js @@ -0,0 +1,19 @@ +// Commerce API - Category 단건 조회 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.category.detail('CATEGORY_ID_HERE') + console.log('Category Detail:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/categoryList.js b/test/commerce/categoryList.js new file mode 100644 index 0000000..32d63dc --- /dev/null +++ b/test/commerce/categoryList.js @@ -0,0 +1,19 @@ +// Commerce API - Category 트리 조회 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.category.list() + console.log('Category List:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/categoryUpdate.js b/test/commerce/categoryUpdate.js new file mode 100644 index 0000000..456bcd9 --- /dev/null +++ b/test/commerce/categoryUpdate.js @@ -0,0 +1,24 @@ +// Commerce API - Category 수정 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.category.update({ + category_id: 'CATEGORY_ID_HERE', + name: 'SDK Test Category (updated)', + status_display: true, + status_best: true + }) + console.log('Category Update:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/couponAvailable.js b/test/commerce/couponAvailable.js new file mode 100644 index 0000000..934adee --- /dev/null +++ b/test/commerce/couponAvailable.js @@ -0,0 +1,19 @@ +// Commerce API - 다운로드 가능한 쿠폰 목록 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.coupon.available() + console.log('Coupon Available:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/couponDownload.js b/test/commerce/couponDownload.js new file mode 100644 index 0000000..d84cf8e --- /dev/null +++ b/test/commerce/couponDownload.js @@ -0,0 +1,21 @@ +// Commerce API - 쿠폰 다운로드 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.coupon.download({ + coupon_template_id: 'COUPON_TEMPLATE_ID_HERE' + }) + console.log('Coupon Download:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/couponList.js b/test/commerce/couponList.js new file mode 100644 index 0000000..dd98d2b --- /dev/null +++ b/test/commerce/couponList.js @@ -0,0 +1,19 @@ +// Commerce API - 사용자 보유 쿠폰 목록 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.coupon.list({ page: 1, limit: 10 }) + console.log('Coupon List:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/couponPreview.js b/test/commerce/couponPreview.js new file mode 100644 index 0000000..58fa745 --- /dev/null +++ b/test/commerce/couponPreview.js @@ -0,0 +1,22 @@ +// Commerce API - 쿠폰 적용 미리보기 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.coupon.preview({ + coupon_ids: [], + order_items: [] + }) + console.log('Coupon Preview:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionRequestList.js b/test/commerce/orderSubscriptionRequestList.js new file mode 100644 index 0000000..d28f792 --- /dev/null +++ b/test/commerce/orderSubscriptionRequestList.js @@ -0,0 +1,19 @@ +// Commerce API - OrderSubscription Request 조회 (본인 모드) + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.orderSubscriptionRequest.list({ page: 1, limit: 10 }) + console.log('OrderSubscription Request List:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/pointBalance.js b/test/commerce/pointBalance.js new file mode 100644 index 0000000..2c37987 --- /dev/null +++ b/test/commerce/pointBalance.js @@ -0,0 +1,19 @@ +// Commerce API - 적립금 잔액 조회 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.point.balance() + console.log('Point Balance:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/pointCalculateLimit.js b/test/commerce/pointCalculateLimit.js new file mode 100644 index 0000000..cc59f00 --- /dev/null +++ b/test/commerce/pointCalculateLimit.js @@ -0,0 +1,19 @@ +// Commerce API - 적립금 사용 한도 계산 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.point.calculateLimit({ order_total: 10000 }) + console.log('Point Calculate Limit:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/pointPreviewUsage.js b/test/commerce/pointPreviewUsage.js new file mode 100644 index 0000000..0bba16c --- /dev/null +++ b/test/commerce/pointPreviewUsage.js @@ -0,0 +1,22 @@ +// Commerce API - 적립금 사용 미리보기 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.point.previewUsage({ + amount: 1000, + order_total: 10000 + }) + console.log('Point Preview Usage:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/pointTransactions.js b/test/commerce/pointTransactions.js new file mode 100644 index 0000000..ea2514d --- /dev/null +++ b/test/commerce/pointTransactions.js @@ -0,0 +1,19 @@ +// Commerce API - 적립금 내역 조회 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: 'hxS-Up--5RvT6oU6QJE0JA', + secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', + mode: 'development' + }) + + try { + await commerce.getAccessToken() + const response = await commerce.point.transactions({ page: 1, limit: 10 }) + console.log('Point Transactions:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() From 21d0207c6999dbeb752c35705a20a6d3d146685e Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Wed, 6 May 2026 13:11:21 +0900 Subject: [PATCH 101/117] deprecate http_status type field (non-breaking) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WalletPaymentResponseParameters.http_status 에 @deprecated JSDoc 추가. type 정의는 그대로 유지 — 가맹점 TypeScript 코드는 컴파일 경고만 발생, 실행 동작은 변함 없음. 다음 메이저 버전에서 제거 예정. 성공 여부는 status 필드로 판단 권장. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/response.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/response.ts b/src/lib/response.ts index af72e06..8fb0f31 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -435,6 +435,7 @@ export interface WalletPaymentResponseParameters { pg: string status_locale: string currency: string + /** @deprecated HTTP status code 노출 type. 다음 메이저 버전에서 제거 예정. 성공 여부는 status 필드 사용. */ http_status: number order_id: string requested_at: string From a6fd7b557d077f7a197c42091ad641e60ca2ae62 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Fri, 8 May 2026 15:06:34 +0900 Subject: [PATCH 102/117] chore: add .env.example + ignore env/tool runtime dirs - .env / .env.* gitignored (.env.example committed as template) - .omj/ tool runtime dir gitignored Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.example | 23 +++++++++++++++++++++++ .gitignore | 8 ++++++++ 2 files changed, 31 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3bf1e1c --- /dev/null +++ b/.env.example @@ -0,0 +1,23 @@ +# Bootpay server SDK example/test credentials +BOOTPAY_ENV=production + +# PG 인증 방식: new (client_key/secret_key, default) 또는 legacy (application_id/private_key) +BOOTPAY_AUTH_MODE=new + +# PG API - recommended client_key/secret_key +BOOTPAY_PG_CLIENT_KEY_PROD=OdKci2s0ux9iyFWsgYHdKw +BOOTPAY_PG_SECRET_KEY_PROD=L15AxIjXxGwFj9xe7NUUOt9VPHqP-CvyXJuK-FqMHto= +BOOTPAY_PG_CLIENT_KEY_DEV=K1Xok7RzFxbT7zMBmiBXNw +BOOTPAY_PG_SECRET_KEY_DEV=vcd_5OXoQAxTA8JSg2VGaSnwmQPkd8DgQ6xiyL6QkyE= + +# PG API - legacy application_id/private_key (호환성 검증용) +BOOTPAY_PG_APPLICATION_ID_PROD=5b8f6a4d396fa665fdc2b5ea +BOOTPAY_PG_PRIVATE_KEY_PROD=rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw= +BOOTPAY_PG_APPLICATION_ID_DEV=59bfc738e13f337dbd6ca48a +BOOTPAY_PG_PRIVATE_KEY_DEV=pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0= + +# Commerce API +BOOTPAY_COMMERCE_CLIENT_KEY_PROD=K1Xok7RzFxbT7zMBmiBXNw +BOOTPAY_COMMERCE_SECRET_KEY_PROD=vcd_5OXoQAxTA8JSg2VGaSnwmQPkd8DgQ6xiyL6QkyE= +BOOTPAY_COMMERCE_CLIENT_KEY_DEV=ZYEi9d93uIaQFEuxXEZfyQ +BOOTPAY_COMMERCE_SECRET_KEY_DEV=j8ONDlZQVHgAWq52g97pGNCqxahGatyZKuC2O09r9MM= diff --git a/.gitignore b/.gitignore index d1044a8..c0e9f31 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,11 @@ package-lock.json yarn.lock dist test/requestSubscribeRest.js + +# Environment files +.env +.env.* +!.env.example + +# Tool runtime dirs +.omj/ From 5dc5f584cd1dd841321160944efaaa29e08ea3fd Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Fri, 8 May 2026 15:06:57 +0900 Subject: [PATCH 103/117] =?UTF-8?q?feat(auth):=20client=5Fkey/secret=5Fkey?= =?UTF-8?q?=20Basic=20Auth=20=EC=A7=80=EC=9B=90=20(PG=20+=20Commerce)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PG resource interceptor: ck/sk 우선, 없으면 application_id/private_key Bearer fallback - getAccessToken: ck/sk 모드는 합성 응답 반환 (request/token 호출 불필요) - ck 또는 sk 한쪽만 입력 + legacy 키도 없으면 NEED_CLIENT_KEY(-101) reject - Commerce resource interceptor: 항상 Basic Auth 사용 (token 분기 제거) - Wallet 타입/메서드는 @deprecated 표시만 추가, 다음 메이저에서 제거 예정 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/bootpay.ts | 20 ++++++++++++++++++-- src/lib/commerce-resource.ts | 10 +++------- src/lib/resource.ts | 6 +++--- src/lib/response.ts | 3 ++- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/bootpay.ts b/src/bootpay.ts index c438028..2101e73 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -22,7 +22,9 @@ import { SubscribePaymentLookupResponse, SubscriptionBillingTransferRequestParameters, SubscriptionPaymentRequestParameters, - WalletRequestParameters, WalletDataPart, WalletPaymentResponseParameters + WalletDataPart, + WalletRequestParameters, + WalletPaymentResponseParameters } from './lib/response' class BootpayBackendNodejs extends BootpayBackendNodejsResource { @@ -37,7 +39,19 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { */ async getAccessToken(): Promise { try { - const { application_id, private_key } = this.bootpayConfiguration + const { application_id, private_key, client_key, secret_key } = this.bootpayConfiguration + const hasLegacyCredentials = application_id && private_key + if ((client_key && !secret_key) || (!client_key && secret_key && !hasLegacyCredentials)) { + return Promise.reject({ + error_code: -101, + message: 'client_key/secret_key를 함께 입력해주세요.' + }) + } + // client_key/secret_key 인증은 매 요청 인터셉터가 Basic Auth 헤더를 직접 부착한다. + // request/token 호출이 불필요하므로, 호환을 위해 합성 응답만 즉시 반환한다. + if (client_key && secret_key) { + return Promise.resolve({ access_token: '', expire_in: 0 }) + } const response: AccessTokenResponseParameters = await this.post('request/token', { application_id, private_key @@ -430,6 +444,7 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { * 등록된 지갑 리스트 가져오기 * Comment by ehowlsla * @date: 2025-03-16 + * @deprecated 다음 메이저 버전에서 제거 예정. wallet 엔드포인트는 폐기 예정이며, 결제는 Request::PaymentController#create 의 wallet_id + user_token 으로 처리됩니다. */ async getUserWallets(user_id: string, sandbox: boolean): Promise { try { @@ -450,6 +465,7 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { // } // } + /** @deprecated wallet 엔드포인트는 폐기 예정. 다음 메이저 버전에서 제거됩니다. wallet_id + user_token 흐름으로 전환하세요. */ async requestWalletPayment(walletRequest: WalletRequestParameters) { try { const response: WalletPaymentResponseParameters = await this.post('wallet/payment', { diff --git a/src/lib/commerce-resource.ts b/src/lib/commerce-resource.ts index 592740b..6d70ab0 100644 --- a/src/lib/commerce-resource.ts +++ b/src/lib/commerce-resource.ts @@ -76,13 +76,9 @@ export class BootpayCommerceResource { config.headers.set('BOOTPAY-SDK-TYPE', '301') config.headers.set('BOOTPAY-ROLE', this.$role || 'user') - if (this.$token !== undefined) { - config.headers.set('Authorization', `Bearer ${this.$token}`) - } else { - const basicAuth = this.getBasicAuthHeader() - if (basicAuth) { - config.headers.set('Authorization', basicAuth) - } + const basicAuth = this.getBasicAuthHeader() + if (basicAuth) { + config.headers.set('Authorization', basicAuth) } return config }, diff --git a/src/lib/resource.ts b/src/lib/resource.ts index f034da6..778cae4 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -67,11 +67,11 @@ export class BootpayBackendNodejsResource { // @ts-expect-error this.$http.interceptors.request.use((config: AxiosRequestConfig) => { if (config.headers !== undefined) { - const { client_key, secret_key, application_id, private_key } = this.bootpayConfiguration + const { client_key, secret_key } = this.bootpayConfiguration // 인증 우선순위: - // 1) client_key가 있으면 Basic(client_key:secret_key) - // 2) application_id가 있으면 Bearer(token) 우선, token 미존재 시 Basic(application_id:private_key) fallback + // 1) client_key/secret_key가 있으면 새 Basic Auth 사용 + // 2) 없으면 기존 application_id/private_key token 방식 유지 if (client_key && secret_key) { config.headers.authorization = `Basic ${Buffer.from(`${client_key}:${secret_key}`).toString('base64')}` } else { diff --git a/src/lib/response.ts b/src/lib/response.ts index 8fb0f31..d0bee9a 100644 --- a/src/lib/response.ts +++ b/src/lib/response.ts @@ -398,6 +398,7 @@ export interface SubscribePaymentLookupResponse { status: number } +/** @deprecated wallet 엔드포인트는 폐기 예정이며, 결제는 wallet_id + user_token 방식으로 전환 예정. 다음 메이저 버전에서 제거됩니다. */ export interface WalletRequestParameters { user_id: string order_name: string @@ -406,7 +407,6 @@ export interface WalletRequestParameters { order_id: string webhook_url?: string content_type?: 'application/json' | 'application/x-www-form-urlencoded' - // order_id?: string items?: ItemModel user?: UserModel extra?: ExtraModel @@ -414,6 +414,7 @@ export interface WalletRequestParameters { sandbox: boolean } +/** @deprecated wallet 엔드포인트는 폐기 예정이며, 결제는 wallet_id + user_token 방식으로 전환 예정. 다음 메이저 버전에서 제거됩니다. */ export interface WalletPaymentResponseParameters { cancelled_price: number wallet_data: WalletData From 5abc0106aa6e0dba22df123a73fb248214e0791c Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Fri, 8 May 2026 15:07:16 +0900 Subject: [PATCH 104/117] =?UTF-8?q?feat(commerce):=20cart=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80=20=E2=80=94=20orderPreview=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - POST /v1/cart/order-preview 권위적 배송비/할인 계산 응답 - guest mode: cart_items 직접 전달, member mode: 서버 장바구니 사용 - types: CartItemPayload, OrderPreviewParams/Response, DeliveryGroup 등 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/bootpay-commerce.ts | 3 ++ src/lib/commerce/modules/cart.ts | 23 +++++++++ src/lib/commerce/modules/index.ts | 1 + src/lib/commerce/types/cart.ts | 84 +++++++++++++++++++++++++++++++ src/lib/commerce/types/index.ts | 1 + 5 files changed, 112 insertions(+) create mode 100644 src/lib/commerce/modules/cart.ts create mode 100644 src/lib/commerce/types/cart.ts diff --git a/src/bootpay-commerce.ts b/src/bootpay-commerce.ts index 4d850e6..48bd1b9 100644 --- a/src/bootpay-commerce.ts +++ b/src/bootpay-commerce.ts @@ -12,6 +12,7 @@ import { OrderSubscriptionRequestModule } from './lib/commerce/modules/order-sub import { CategoryModule } from './lib/commerce/modules/category' import { CouponModule } from './lib/commerce/modules/coupon' import { PointModule } from './lib/commerce/modules/point' +import { CartModule } from './lib/commerce/modules/cart' import { StoreModule } from './lib/commerce/modules/store' export interface CommerceTokenResponse { @@ -33,6 +34,7 @@ export class BootpayCommerce extends BootpayCommerceResource { public category!: CategoryModule public coupon!: CouponModule public point!: PointModule + public cart!: CartModule public store!: StoreModule constructor(configuration?: CommerceConfiguration) { @@ -57,6 +59,7 @@ export class BootpayCommerce extends BootpayCommerceResource { this.category = new CategoryModule(this) this.coupon = new CouponModule(this) this.point = new PointModule(this) + this.cart = new CartModule(this) this.store = new StoreModule(this) } diff --git a/src/lib/commerce/modules/cart.ts b/src/lib/commerce/modules/cart.ts new file mode 100644 index 0000000..c02daf1 --- /dev/null +++ b/src/lib/commerce/modules/cart.ts @@ -0,0 +1,23 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { OrderPreviewParams, OrderPreviewResponse } from '../types' + +export class CartModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 주문 미리보기 (배송비/할인 권위적 계산) + * POST /v1/cart/order-preview + * + * member_mode='guest' (기본): cart_items 필수 + * member_mode='member': 서버 장바구니 사용 (user 토큰 필요) + */ + async orderPreview( + params: OrderPreviewParams = {} + ): Promise> { + return this.bootpay.post('cart/order-preview', params) + } +} diff --git a/src/lib/commerce/modules/index.ts b/src/lib/commerce/modules/index.ts index 59253ce..5587cc8 100644 --- a/src/lib/commerce/modules/index.ts +++ b/src/lib/commerce/modules/index.ts @@ -11,5 +11,6 @@ export * from './order-subscription-request' export * from './category' export * from './coupon' export * from './point' +export * from './cart' export * from './store' diff --git a/src/lib/commerce/types/cart.ts b/src/lib/commerce/types/cart.ts new file mode 100644 index 0000000..e7c33b6 --- /dev/null +++ b/src/lib/commerce/types/cart.ts @@ -0,0 +1,84 @@ +export interface CartItemPayload { + product_id: string + product_option_id?: string + quantity?: number + is_subscription?: boolean + subscription_period_id?: string +} + +export interface ShippingAddressPayload { + zipcode?: string +} + +export interface OrderPreviewParams { + member_mode?: 'guest' | 'member' + cart_items?: CartItemPayload[] + shipping_address?: ShippingAddressPayload + coupon_ids?: string[] + point_amount?: number + user_group_id?: string +} + +export interface DeliveryGroupItem { + cart_item_id?: string + product_id: string + product_option_id?: string + product_name?: string + quantity: number + price: number + subtotal?: number +} + +export interface DeliveryGroup { + group_key?: string + seller_id?: string + delivery_shipping_id?: string + delivery_shipping_bundle_id?: string + bundle_id?: string + items: DeliveryGroupItem[] + total_price: number + total_quantity: number + delivery_fee: number + delivery_extra_fee_jeju?: number + delivery_extra_fee_remote?: number + shipping_available?: boolean +} + +export interface AppliedCouponSnapshot { + coupon_id?: string + coupon_template_id?: string + name?: string + discount_type?: number + discount_value?: number + actual_discount_amount?: number + [key: string]: unknown +} + +export interface OrderPreviewSummary { + total_items: number + total_quantity: number + total_product_price: number + total_delivery_fee: number + total_delivery_extra_fee: number + coupon_discount_amount: number + applied_coupons: AppliedCouponSnapshot[] + point_use_amount: number + point_max_usable: number + point_balance_after: number + total_order_price: number +} + +export interface OrderPreviewUnavailableItem { + cart_item_id?: string + product_id: string + product_name?: string + reason?: string +} + +export interface OrderPreviewResponse { + cart_id?: string + user_id?: string + delivery_groups: DeliveryGroup[] + summary: OrderPreviewSummary + unavailable_items?: OrderPreviewUnavailableItem[] +} diff --git a/src/lib/commerce/types/index.ts b/src/lib/commerce/types/index.ts index e378857..deb8d43 100644 --- a/src/lib/commerce/types/index.ts +++ b/src/lib/commerce/types/index.ts @@ -12,3 +12,4 @@ export * from './order-subscription-request' export * from './category' export * from './coupon' export * from './point' +export * from './cart' From 9e096c4752ddfdbd02aae785429f434fb197cffb Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Fri, 8 May 2026 15:07:26 +0900 Subject: [PATCH 105/117] =?UTF-8?q?docs(readme):=20client=5Fkey/secret=5Fk?= =?UTF-8?q?ey=20=EC=82=AC=EC=9A=A9=20=EC=95=88=EB=82=B4=20+=20.env=20?= =?UTF-8?q?=EC=9D=B8=ED=94=84=EB=9D=BC=20=EA=B0=80=EC=9D=B4=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 권장 인증: client_key/secret_key, legacy application_id/private_key 호환 - .env / .env.example 사용 패턴 + BOOTPAY_PG_*, BOOTPAY_COMMERCE_* 변수 - 예제 코드를 process.env.* 참조로 통일 (하드코딩 키 제거) Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 141 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 4b69726..1340ebd 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 ## 목차 - [PG API 사용하기](#사용하기) -- [2. 결제 단건 조회](#2-결제-단건-조회) + - [1. 토큰 발급](#1-토큰-발급) + - [2. 결제 단건 조회](#2-결제-단건-조회) - [3. 결제 취소 (전액 취소 / 부분 취소)](#3-결제-취소-전액-취소--부분-취소) - [4. 자동/빌링/정기 결제](#4-자동빌링정기-결제) - [4-1. 카드 빌링키 발급](#4-1-카드-빌링키-발급) @@ -50,17 +51,54 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 npm install --save @bootpay/backend-js ``` + +## 환경변수 설정 + +예제와 테스트는 각 SDK 루트의 `.env` 파일을 우선 읽습니다. 먼저 `.env.example`을 복사한 뒤 필요한 키만 변경하세요. `.env`는 gitignore 처리되어 커밋되지 않습니다. + +```bash +cp .env.example .env +# BOOTPAY_ENV=production 또는 development +``` + +주요 변수: + +```env +BOOTPAY_ENV=production +BOOTPAY_PG_CLIENT_KEY_PROD=... +BOOTPAY_PG_SECRET_KEY_PROD=... +BOOTPAY_PG_CLIENT_KEY_DEV=... +BOOTPAY_PG_SECRET_KEY_DEV=... +BOOTPAY_COMMERCE_CLIENT_KEY_PROD=... +BOOTPAY_COMMERCE_SECRET_KEY_PROD=... +BOOTPAY_COMMERCE_CLIENT_KEY_PROD=... +BOOTPAY_COMMERCE_SECRET_KEY_PROD=... +``` + +변수가 없으면 SDK 테스트용 기본값(NodeJS 기준 ck/sk)으로 fallback 합니다. + # 사용하기 +> 권장 인증 방식은 `client_key/secret_key`입니다. 기존 `application_id/private_key` 설정도 하위 호환을 위해 계속 동작합니다. 둘 다 설정된 경우 `client_key/secret_key`가 우선됩니다. + +```javascript +// Legacy fallback: +// Bootpay.setConfiguration({ +// application_id: process.env.BOOTPAY_APPLICATION_ID, +// private_key: process.env.BOOTPAY_PRIVATE_KEY +// }) +``` + ```javascript import { Bootpay } from "@bootpay/backend-js"; (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.cancelPayment({ receipt_id: '628b2206d01c7e00209b6087', cancel_price: 1000, @@ -75,16 +113,39 @@ import { Bootpay } from "@bootpay/backend-js"; ``` +## 1. 토큰 발급 + +부트페이와 서버간 통신을 하기 위해서는 부트페이 서버로부터 토큰을 발급받아야 합니다. +발급된 토큰은 30분간 유효하며, 최초 발급일로부터 30분이 지날 경우 토큰 발급 함수를 재호출 해주셔야 합니다. + +```javascript +(async () => { + Bootpay.setConfiguration({ + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD + }) + try { + let response = await Bootpay.getAccessToken() + console.log(response) + } catch (e) { + console.log(e) + } +})() + +``` + + ## 2. 결제 단건 조회 결제창 및 정기결제에서 승인/취소된 결제건에 대하여 올바른 결제건인지 서버간 통신으로 결제검증을 합니다. ```javascript (async () => { const Bootpay = require('@bootpay/backend-js').Bootpay Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.receiptPayment('62b12f4b6262500007629fec') console.log(response) } catch (e) { @@ -106,10 +167,11 @@ price를 지정하지 않으면 전액취소 됩니다. ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.cancelPayment({ receipt_id: '628b2206d01c7e00209b6087', cancel_price: 1000, @@ -131,10 +193,11 @@ REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에 ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribeBillingKey({ pg: '나이스페이', order_name: '테스트결제', @@ -162,10 +225,11 @@ REST API 방식으로 고객의 계좌 정보를 전달하여, PG사에게 빌 ```javascript (async () => { Bootpay.setConfiguration({ - client_key: '5b8f6a4d396fa665fdc2b5ea', - secret_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribeAutomaticTransferBillingKey({ pg: '나이스페이', order_name: '테스트결제', @@ -193,6 +257,7 @@ REST API 방식으로 고객의 계좌 정보를 전달하여, PG사에게 빌 이후 빌링키 발급 요청시 응답받은 receipt_id로, 출금 동의 확인을 요청합니다. ```javascript try { + await Bootpay.getAccessToken() const response = await Bootpay.publishAutomaticTransferBillingKey('6655069ca691573f1bb9c28a') console.log(response) } catch (e) { @@ -208,10 +273,11 @@ try { ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribeCardPayment({ billing_key: '62b3d166cf9f6d001bd20d59', order_name: '테스트 결제', @@ -230,11 +296,12 @@ try { ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { // console.log(new Date((new Date()).getTime() + 5000)) + await Bootpay.getAccessToken() const response = await Bootpay.subscribePaymentReserve({ billing_key: '62b3d166cf9f6d001bd20d59', order_name: '테스트 결제', @@ -262,11 +329,12 @@ await Bootpay.subscribePaymentReserveLookup(reserve_id) ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { // console.log(new Date((new Date()).getTime() + 5000)) + await Bootpay.getAccessToken() const response = await Bootpay.subscribePaymentReserve({ billing_key: '62b3d166cf9f6d001bd20d59', order_name: '테스트 결제', @@ -289,10 +357,11 @@ await Bootpay.subscribePaymentReserveLookup(reserve_id) ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.destroyBillingKey('62b3d166cf9f6d001bd20d59') console.log(response) } catch (e) { @@ -307,10 +376,11 @@ await Bootpay.subscribePaymentReserveLookup(reserve_id) ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.lookupSubscribeBillingKey('62b3cbbecf9f6d001bd20ce8') console.log(response) } catch (e) { @@ -332,10 +402,11 @@ console.log(response) ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.requestUserToken({ user_id: 'gosomi1', phone:'01012345678' @@ -359,10 +430,11 @@ console.log(response) ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.confirmPayment('62876963d01c7e00209b6028') console.log(response) } catch (e) { @@ -377,10 +449,11 @@ console.log(response) ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.certificate('625783a6cf9f6d001d0aed19') console.log(response) } catch (e) { @@ -397,10 +470,11 @@ PG사로 배송정보( 이니시스, KCP만 지원 )를 보내서 에스크로 ```javascript (async () => { Bootpay.setConfiguration({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=' + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD }) try { + await Bootpay.getAccessToken() const response = await Bootpay.shippingStart({ receipt_id: "62a9379ad01c7e001f7dc1f3", tracking_number: '123456', @@ -429,10 +503,13 @@ PG사로 배송정보( 이니시스, KCP만 지원 )를 보내서 에스크로 const { BootpayCommerce } = require('@bootpay/backend-js') const commerce = new BootpayCommerce({ - client_key: 'r8KT2-2w9iov6IgY93pnuA', - secret_key: 't8sUJ1z07a8uoN-iVIeN4nVrWdmZ8s5NoqXGkPBymqs=', - mode: 'development' // 'production' | 'development' | 'stage' + client_key: process.env.BOOTPAY_COMMERCE_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_COMMERCE_SECRET_KEY_PROD, + mode: 'production' // 'production' | 'development' | 'stage' }) + +// 토큰 발급 +await commerce.getAccessToken() ``` ### 10-2. 사용자 관리 From 268f1f6f466fde38bd48c1dc5d279f8c49468739 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Fri, 8 May 2026 15:08:19 +0900 Subject: [PATCH 106/117] =?UTF-8?q?test:=20PG=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=94=94=EB=A0=89=ED=84=B0=EB=A6=AC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20+=20auth=20=EB=AA=A8=EB=93=9C=20=ED=86=A0=EA=B8=80?= =?UTF-8?q?=20=EC=9D=B8=ED=94=84=EB=9D=BC=20+=20=ED=95=98=EB=93=9C?= =?UTF-8?q?=EC=BD=94=EB=94=A9=20=ED=82=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test/*.js → test/pg/*.js 로 분리 (commerce 와 구조 통일) - BOOTPAY_AUTH_MODE=new|legacy 토글로 ck/sk vs application_id 양쪽 검증 - create_pg_api 호출 시 stdout 에 활성 모드 출력 - TEST_DATA 참조로 통일, 코드-레벨 하드코딩 receipt_id/billing_key 제거 - 신규: cartOrderPreview, legacyCompatibility 스모크 - test.md: auth 모드 사용법 문서화 Co-Authored-By: Claude Opus 4.7 (1M context) --- test/authenticateConfirmRest.mjs | 20 ---- test/authenticateRealarmRest.js | 15 --- test/axios_test.js | 20 ---- test/cancelPayment.js | 19 --- test/cancelSubscribeReserve.js | 26 ---- test/cashReceiptPublishOnReceipt.js | 33 ----- test/certificate.js | 14 --- test/commerce/cartOrderPreview.js | 42 +++++++ test/commerce/categoryCreate.js | 11 +- test/commerce/categoryDelete.js | 11 +- test/commerce/categoryDetail.js | 11 +- test/commerce/categoryList.js | 11 +- test/commerce/categoryUpdate.js | 11 +- test/commerce/couponAvailable.js | 11 +- test/commerce/couponDownload.js | 11 +- test/commerce/couponList.js | 11 +- test/commerce/couponPreview.js | 11 +- test/commerce/getAccessToken.js | 8 +- test/commerce/invoiceCreate.js | 11 +- test/commerce/invoiceDetail.js | 11 +- test/commerce/invoiceList.js | 11 +- test/commerce/invoiceNotify.js | 11 +- test/commerce/orderCancelApprove.js | 11 +- test/commerce/orderCancelList.js | 11 +- test/commerce/orderCancelReject.js | 11 +- test/commerce/orderCancelRequest.js | 11 +- test/commerce/orderCancelWithdraw.js | 11 +- test/commerce/orderDetail.js | 11 +- test/commerce/orderList.js | 11 +- test/commerce/orderMonth.js | 11 +- .../orderSubscriptionAdjustmentCreate.js | 11 +- .../orderSubscriptionAdjustmentDelete.js | 11 +- .../orderSubscriptionAdjustmentUpdate.js | 11 +- test/commerce/orderSubscriptionBillDetail.js | 11 +- test/commerce/orderSubscriptionBillList.js | 11 +- test/commerce/orderSubscriptionBillUpdate.js | 11 +- ...rderSubscriptionCalculateTerminationFee.js | 11 +- test/commerce/orderSubscriptionDetail.js | 11 +- test/commerce/orderSubscriptionList.js | 11 +- test/commerce/orderSubscriptionPause.js | 11 +- test/commerce/orderSubscriptionRequestList.js | 11 +- test/commerce/orderSubscriptionResume.js | 11 +- test/commerce/orderSubscriptionTermination.js | 11 +- test/commerce/orderSubscriptionUpdate.js | 11 +- test/commerce/pointBalance.js | 11 +- test/commerce/pointCalculateLimit.js | 11 +- test/commerce/pointPreviewUsage.js | 11 +- test/commerce/pointTransactions.js | 11 +- test/commerce/productCreate.js | 11 +- test/commerce/productDelete.js | 11 +- test/commerce/productDetail.js | 11 +- test/commerce/productList.js | 11 +- test/commerce/productStatus.js | 11 +- test/commerce/productUpdate.js | 11 +- test/commerce/userAuthenticationData.js | 11 +- test/commerce/userCheckExist.js | 11 +- test/commerce/userDelete.js | 11 +- test/commerce/userDetail.js | 11 +- .../commerce/userGroupAggregateTransaction.js | 11 +- test/commerce/userGroupCreate.js | 11 +- test/commerce/userGroupDetail.js | 11 +- test/commerce/userGroupLimit.js | 11 +- test/commerce/userGroupList.js | 11 +- test/commerce/userGroupUpdate.js | 11 +- test/commerce/userGroupUserCreate.js | 11 +- test/commerce/userGroupUserDelete.js | 11 +- test/commerce/userJoin.js | 11 +- test/commerce/userList.js | 11 +- test/commerce/userLogin.js | 11 +- test/commerce/userToken.js | 11 +- test/commerce/userUpdate.js | 11 +- test/config.js | 113 +++++++++++++++--- test/confirmPayment.js | 15 --- test/destroySubscribeBillingKey.js | 14 --- test/getAccessToken.js | 16 --- test/getUserWallets.js | 17 --- test/lookupBilling.js | 34 ------ test/lookupSubscribeBilling.js | 14 --- test/pg/authenticateConfirmRest.mjs | 18 +++ test/pg/authenticateRealarmRest.js | 15 +++ test/{ => pg}/authenticateRequestRest.js | 10 +- test/pg/axios_test.js | 27 +++++ test/pg/cancelPayment.js | 9 +- test/pg/cancelSubscribeReserve.js | 9 +- test/pg/cashReceiptPublishOnReceipt.js | 9 +- test/pg/certificate.js | 9 +- test/pg/confirmPayment.js | 9 +- test/pg/destroySubscribeBillingKey.js | 9 +- test/{ => pg}/form_payment_progress.js | 20 +++- test/pg/getAccessToken.js | 34 +++++- test/{ => pg}/getBillingKey.js | 10 +- test/pg/getUserWallets.js | 17 +++ test/pg/legacyCompatibility.js | 101 ++++++++++++++++ test/pg/lookupBilling.js | 9 +- test/pg/lookupSubscribeBilling.js | 9 +- .../publishAutomaticTransferBillingKey.js | 12 +- test/pg/receiptPayment.js | 9 +- test/{ => pg}/requestCashReceipt.js | 30 ++--- ...estSubscribeAutomaticTransferBillingKey.js | 10 +- test/pg/requestUserToken.js | 9 +- test/{ => pg}/request_payment.js | 2 +- test/pg/shippingStart.js | 9 +- test/pg/subscribeCardPayment.js | 9 +- test/{ => pg}/subscribePayment.js | 12 +- test/pg/subscribePaymentReserve.js | 9 +- test/receiptPayment.js | 16 --- test/requestUserToken.js | 17 --- test/shippingStart.js | 24 ---- test/subscribeCardPayment.js | 20 ---- test/subscribePaymentReserve.js | 22 ---- test/test.md | 58 +++++++++ test/walletPayment.js | 25 ---- 112 files changed, 933 insertions(+), 786 deletions(-) delete mode 100644 test/authenticateConfirmRest.mjs delete mode 100644 test/authenticateRealarmRest.js delete mode 100644 test/axios_test.js delete mode 100644 test/cancelPayment.js delete mode 100644 test/cancelSubscribeReserve.js delete mode 100644 test/cashReceiptPublishOnReceipt.js delete mode 100644 test/certificate.js create mode 100644 test/commerce/cartOrderPreview.js delete mode 100644 test/confirmPayment.js delete mode 100644 test/destroySubscribeBillingKey.js delete mode 100644 test/getAccessToken.js delete mode 100644 test/getUserWallets.js delete mode 100644 test/lookupBilling.js delete mode 100644 test/lookupSubscribeBilling.js create mode 100644 test/pg/authenticateConfirmRest.mjs create mode 100644 test/pg/authenticateRealarmRest.js rename test/{ => pg}/authenticateRequestRest.js (68%) create mode 100644 test/pg/axios_test.js rename test/{ => pg}/form_payment_progress.js (72%) rename test/{ => pg}/getBillingKey.js (75%) create mode 100644 test/pg/getUserWallets.js create mode 100644 test/pg/legacyCompatibility.js rename test/{ => pg}/publishAutomaticTransferBillingKey.js (86%) rename test/{ => pg}/requestCashReceipt.js (52%) rename test/{ => pg}/requestSubscribeAutomaticTransferBillingKey.js (84%) rename test/{ => pg}/request_payment.js (93%) rename test/{ => pg}/subscribePayment.js (53%) delete mode 100644 test/receiptPayment.js delete mode 100644 test/requestUserToken.js delete mode 100644 test/shippingStart.js delete mode 100644 test/subscribeCardPayment.js delete mode 100644 test/subscribePaymentReserve.js delete mode 100644 test/walletPayment.js diff --git a/test/authenticateConfirmRest.mjs b/test/authenticateConfirmRest.mjs deleted file mode 100644 index 4d3ea63..0000000 --- a/test/authenticateConfirmRest.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { Bootpay } from "../dist/bootpay.js" - -(async () => { - // const Bootpay = require('../esm/dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - const token = await Bootpay.getAccessToken() - console.log(token) - const response = await Bootpay.confirmAuthentication( - '63688775cf9f6d0023b85f2b', - '457670' - ) - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/authenticateRealarmRest.js b/test/authenticateRealarmRest.js deleted file mode 100644 index d3f9b33..0000000 --- a/test/authenticateRealarmRest.js +++ /dev/null @@ -1,15 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - // console.log(new Date((new Date()).getTime() + 5000)) - await Bootpay.getAccessToken() - const response = await Bootpay.realarmAuthentication('63688e6dd01c7e00211cbd0a') - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/axios_test.js b/test/axios_test.js deleted file mode 100644 index eee43a1..0000000 --- a/test/axios_test.js +++ /dev/null @@ -1,20 +0,0 @@ - -(async () => { - const RestClient = require('../dist/bootpay').Bootpay - RestClient.setConfig( - '5b8f6a4d396fa665fdc2b5ea', - 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - ) - let response = await RestClient.getAccessToken() - // console.log(response) - const axios = require('axios') - try { - response = await axios.post("https://dev-api.bootpay.co.kr/request/token.json", { - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw==' - }) - console.log(response) - } catch(e) { - console.log(e.response.data) - } -})() \ No newline at end of file diff --git a/test/cancelPayment.js b/test/cancelPayment.js deleted file mode 100644 index 6558bfb..0000000 --- a/test/cancelPayment.js +++ /dev/null @@ -1,19 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.cancelPayment({ - receipt_id: '628b2206d01c7e00209b6087', - cancel_price: 1000, - cancel_username: '테스트 사용자', - cancel_message: '테스트 취소입니다.' - }) - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/cancelSubscribeReserve.js b/test/cancelSubscribeReserve.js deleted file mode 100644 index dd3cf0e..0000000 --- a/test/cancelSubscribeReserve.js +++ /dev/null @@ -1,26 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - // console.log(new Date((new Date()).getTime() + 5000)) - await Bootpay.getAccessToken() - const response = await Bootpay.subscribePaymentReserve({ - billing_key: '6406ef293049c8001ff5afd3', - order_name: '테스트 결제', - order_id: (new Date()).getTime(), - price: 1000, - reserve_execute_at: new Date((new Date()).getTime() + 5000) - }) - if (response.reserve_id !== undefined) { - const lookup = await Bootpay.subscribePaymentReserveLookup(response.reserve_id) - console.log(lookup) - const cancel = await Bootpay.cancelSubscribeReserve(response.reserve_id) - console.log(cancel) - } - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/cashReceiptPublishOnReceipt.js b/test/cashReceiptPublishOnReceipt.js deleted file mode 100644 index 9b8a043..0000000 --- a/test/cashReceiptPublishOnReceipt.js +++ /dev/null @@ -1,33 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - // Bootpay.setConfiguration({ - // application_id: '5b8f6a4d396fa665fdc2b5ea', - // private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - // }) - Bootpay.setConfiguration({ - application_id: '59bfc738e13f337dbd6ca48a', - private_key: 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', - mode: 'development' - }) - try { - // console.log(new Date((new Date()).getTime() + 5000)) - await Bootpay.getAccessToken() - const response = await Bootpay.cashReceiptPublishOnReceipt({ - receipt_id: "62e32b3f1fc192036e8db942", - username: '테스트', - email: 'test@bootpay.co.kr', - phone: '01000000000', - identity_no: '01000000000', - cash_receipt_type: '소득공제' - }) - console.log(response) - if (response.receipt_id !== undefined) { - const cancel = await Bootpay.cashReceiptCancelOnReceipt({ - receipt_id: "62e32b3f1fc192036e8db942", - }) - console.log(cancel) - } - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/certificate.js b/test/certificate.js deleted file mode 100644 index 0503ce5..0000000 --- a/test/certificate.js +++ /dev/null @@ -1,14 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.certificate('625783a6cf9f6d001d0aed19') - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/commerce/cartOrderPreview.js b/test/commerce/cartOrderPreview.js new file mode 100644 index 0000000..64d4b5c --- /dev/null +++ b/test/commerce/cartOrderPreview.js @@ -0,0 +1,42 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); +// Commerce API - 장바구니 주문 미리보기 (V1 단일 결제 계산 endpoint) +// +// V1 결제 계산은 cart/order-preview 단일 호출로 처리: +// - cart_items + zipcode → 상품금액 + 배송비 권위 검증 +// - coupon_ids[] → 쿠폰 권위 검증 (coupon_discount_amount + applied_coupons[]) +// - point_amount → 적립금 권위 검증 (point_use_amount + point_max_usable + point_balance_after) +// - summary.total_order_price 가 결제 금액 (= product + delivery - coupon - point) +// +// member_mode='guest' 호출: cart_items 직접 전달 (서버 장바구니 불필요) +// member_mode='member' 호출: 회원 cart 사용 (cart_items 무시) + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode + }) + + try { + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() + const response = await commerce.cart.orderPreview({ + member_mode: 'guest', + cart_items: [ + { + product_id: 'PRODUCT_ID_HERE', + quantity: 1 + } + ], + shipping_address: { zipcode: '63000' }, + coupon_ids: [], + point_amount: 0 + }) + console.log('Cart Order Preview:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/categoryCreate.js b/test/commerce/categoryCreate.js index e4fffc9..3a222cd 100644 --- a/test/commerce/categoryCreate.js +++ b/test/commerce/categoryCreate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Category 생성 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.category.create({ name: 'SDK Test Category', status_display: true, diff --git a/test/commerce/categoryDelete.js b/test/commerce/categoryDelete.js index d2dd03f..37e3ba9 100644 --- a/test/commerce/categoryDelete.js +++ b/test/commerce/categoryDelete.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Category 삭제 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.category.destroy('CATEGORY_ID_HERE') console.log('Category Delete:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/categoryDetail.js b/test/commerce/categoryDetail.js index b2dea37..d05afdb 100644 --- a/test/commerce/categoryDetail.js +++ b/test/commerce/categoryDetail.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Category 단건 조회 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.category.detail('CATEGORY_ID_HERE') console.log('Category Detail:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/categoryList.js b/test/commerce/categoryList.js index 32d63dc..55e1b50 100644 --- a/test/commerce/categoryList.js +++ b/test/commerce/categoryList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Category 트리 조회 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.category.list() console.log('Category List:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/categoryUpdate.js b/test/commerce/categoryUpdate.js index 456bcd9..12d4b31 100644 --- a/test/commerce/categoryUpdate.js +++ b/test/commerce/categoryUpdate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Category 수정 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.category.update({ category_id: 'CATEGORY_ID_HERE', name: 'SDK Test Category (updated)', diff --git a/test/commerce/couponAvailable.js b/test/commerce/couponAvailable.js index 934adee..f8f6c40 100644 --- a/test/commerce/couponAvailable.js +++ b/test/commerce/couponAvailable.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - 다운로드 가능한 쿠폰 목록 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.coupon.available() console.log('Coupon Available:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/couponDownload.js b/test/commerce/couponDownload.js index d84cf8e..8753075 100644 --- a/test/commerce/couponDownload.js +++ b/test/commerce/couponDownload.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - 쿠폰 다운로드 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.coupon.download({ coupon_template_id: 'COUPON_TEMPLATE_ID_HERE' }) diff --git a/test/commerce/couponList.js b/test/commerce/couponList.js index dd98d2b..eb6114f 100644 --- a/test/commerce/couponList.js +++ b/test/commerce/couponList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - 사용자 보유 쿠폰 목록 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.coupon.list({ page: 1, limit: 10 }) console.log('Coupon List:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/couponPreview.js b/test/commerce/couponPreview.js index 58fa745..401bb42 100644 --- a/test/commerce/couponPreview.js +++ b/test/commerce/couponPreview.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - 쿠폰 적용 미리보기 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.coupon.preview({ coupon_ids: [], order_items: [] diff --git a/test/commerce/getAccessToken.js b/test/commerce/getAccessToken.js index 679a848..120b6cb 100644 --- a/test/commerce/getAccessToken.js +++ b/test/commerce/getAccessToken.js @@ -1,12 +1,14 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - getAccessToken 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' // 'production' | 'development' | 'stage' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode // 'production' | 'development' | 'stage' }) try { diff --git a/test/commerce/invoiceCreate.js b/test/commerce/invoiceCreate.js index 2164a59..b4a3e50 100644 --- a/test/commerce/invoiceCreate.js +++ b/test/commerce/invoiceCreate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Invoice Create (청구서 생성) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.invoice.create({ user_id: 'USER_ID_HERE', diff --git a/test/commerce/invoiceDetail.js b/test/commerce/invoiceDetail.js index cbca8bc..8fce591 100644 --- a/test/commerce/invoiceDetail.js +++ b/test/commerce/invoiceDetail.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Invoice Detail (청구서 상세 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.invoice.detail('INVOICE_ID_HERE') console.log('Invoice Detail Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/invoiceList.js b/test/commerce/invoiceList.js index 3bd6ead..19d62a5 100644 --- a/test/commerce/invoiceList.js +++ b/test/commerce/invoiceList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Invoice List (청구서 목록 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // 기본 목록 조회 const response = await commerce.invoice.list() diff --git a/test/commerce/invoiceNotify.js b/test/commerce/invoiceNotify.js index 2452a6b..721e80e 100644 --- a/test/commerce/invoiceNotify.js +++ b/test/commerce/invoiceNotify.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Invoice Notify (청구서 알림 발송) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // send_types: 1=SMS, 2=Email 등 const response = await commerce.invoice.notify('INVOICE_ID_HERE', [1, 2]) diff --git a/test/commerce/orderCancelApprove.js b/test/commerce/orderCancelApprove.js index e18401b..2ad3a05 100644 --- a/test/commerce/orderCancelApprove.js +++ b/test/commerce/orderCancelApprove.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderCancel Approve (취소 승인) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderCancel.approve({ order_cancel_request_history_id: 'ORDER_CANCEL_REQUEST_HISTORY_ID_HERE', diff --git a/test/commerce/orderCancelList.js b/test/commerce/orderCancelList.js index 866afc5..2ec8bb9 100644 --- a/test/commerce/orderCancelList.js +++ b/test/commerce/orderCancelList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderCancel List (취소 요청 목록 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // 기본 목록 조회 const response = await commerce.orderCancel.list() diff --git a/test/commerce/orderCancelReject.js b/test/commerce/orderCancelReject.js index b07263a..f2f5df5 100644 --- a/test/commerce/orderCancelReject.js +++ b/test/commerce/orderCancelReject.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderCancel Reject (취소 거절) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderCancel.reject({ order_cancel_request_history_id: 'ORDER_CANCEL_REQUEST_HISTORY_ID_HERE', diff --git a/test/commerce/orderCancelRequest.js b/test/commerce/orderCancelRequest.js index b1bbe3f..93bc171 100644 --- a/test/commerce/orderCancelRequest.js +++ b/test/commerce/orderCancelRequest.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderCancel Request (취소 요청) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderCancel.request({ order_id: 'ORDER_ID_HERE', diff --git a/test/commerce/orderCancelWithdraw.js b/test/commerce/orderCancelWithdraw.js index 12d0a39..85edf48 100644 --- a/test/commerce/orderCancelWithdraw.js +++ b/test/commerce/orderCancelWithdraw.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderCancel Withdraw (취소 요청 철회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderCancel.withdraw('ORDER_CANCEL_REQUEST_HISTORY_ID_HERE') console.log('OrderCancel Withdraw Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/orderDetail.js b/test/commerce/orderDetail.js index bfbed0b..046ef7a 100644 --- a/test/commerce/orderDetail.js +++ b/test/commerce/orderDetail.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Order Detail (주문 상세 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.order.detail('25112678946009400157') console.log('Order Detail Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/orderList.js b/test/commerce/orderList.js index b212664..a73219d 100644 --- a/test/commerce/orderList.js +++ b/test/commerce/orderList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Order List (주문 목록 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // 기본 목록 조회 const response = await commerce.order.list() diff --git a/test/commerce/orderMonth.js b/test/commerce/orderMonth.js index f2c6fd7..cbf9fca 100644 --- a/test/commerce/orderMonth.js +++ b/test/commerce/orderMonth.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Order Month (월별 주문 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.order.month('USER_GROUP_ID_HERE', '2024-12') console.log('Order Month Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/orderSubscriptionAdjustmentCreate.js b/test/commerce/orderSubscriptionAdjustmentCreate.js index 717099e..9e050f7 100644 --- a/test/commerce/orderSubscriptionAdjustmentCreate.js +++ b/test/commerce/orderSubscriptionAdjustmentCreate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscriptionAdjustment Create (정기구독 조정 생성) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscriptionAdjustment.create( 'ORDER_SUBSCRIPTION_ID_HERE', diff --git a/test/commerce/orderSubscriptionAdjustmentDelete.js b/test/commerce/orderSubscriptionAdjustmentDelete.js index 2189e38..26e725b 100644 --- a/test/commerce/orderSubscriptionAdjustmentDelete.js +++ b/test/commerce/orderSubscriptionAdjustmentDelete.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscriptionAdjustment Delete (정기구독 조정 삭제) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscriptionAdjustment.delete( 'ORDER_SUBSCRIPTION_ID_HERE', diff --git a/test/commerce/orderSubscriptionAdjustmentUpdate.js b/test/commerce/orderSubscriptionAdjustmentUpdate.js index 026309c..8e7db46 100644 --- a/test/commerce/orderSubscriptionAdjustmentUpdate.js +++ b/test/commerce/orderSubscriptionAdjustmentUpdate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscriptionAdjustment Update (정기구독 조정 수정) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscriptionAdjustment.update({ order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', diff --git a/test/commerce/orderSubscriptionBillDetail.js b/test/commerce/orderSubscriptionBillDetail.js index 4d47d80..e464b4a 100644 --- a/test/commerce/orderSubscriptionBillDetail.js +++ b/test/commerce/orderSubscriptionBillDetail.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscriptionBill Detail (정기구독 청구 상세 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscriptionBill.detail('ORDER_SUBSCRIPTION_BILL_ID_HERE') console.log('OrderSubscriptionBill Detail Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/orderSubscriptionBillList.js b/test/commerce/orderSubscriptionBillList.js index f955d6a..f8a6e7f 100644 --- a/test/commerce/orderSubscriptionBillList.js +++ b/test/commerce/orderSubscriptionBillList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscriptionBill List (정기구독 청구 목록 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // 기본 목록 조회 const response = await commerce.orderSubscriptionBill.list() diff --git a/test/commerce/orderSubscriptionBillUpdate.js b/test/commerce/orderSubscriptionBillUpdate.js index d6352b4..32a41ea 100644 --- a/test/commerce/orderSubscriptionBillUpdate.js +++ b/test/commerce/orderSubscriptionBillUpdate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscriptionBill Update (정기구독 청구 수정) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscriptionBill.update({ order_subscription_bill_id: 'ORDER_SUBSCRIPTION_BILL_ID_HERE', diff --git a/test/commerce/orderSubscriptionCalculateTerminationFee.js b/test/commerce/orderSubscriptionCalculateTerminationFee.js index 7c52890..0e4ec8a 100644 --- a/test/commerce/orderSubscriptionCalculateTerminationFee.js +++ b/test/commerce/orderSubscriptionCalculateTerminationFee.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscription Calculate Termination Fee (해지 수수료 계산) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // order_subscription_id로 조회 const response = await commerce.orderSubscription.requestIng.calculateTerminationFee( diff --git a/test/commerce/orderSubscriptionDetail.js b/test/commerce/orderSubscriptionDetail.js index 67ddc77..5f6f347 100644 --- a/test/commerce/orderSubscriptionDetail.js +++ b/test/commerce/orderSubscriptionDetail.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscription Detail (정기구독 상세 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscription.detail('ORDER_SUBSCRIPTION_ID_HERE') console.log('OrderSubscription Detail Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/orderSubscriptionList.js b/test/commerce/orderSubscriptionList.js index 8353338..0c04db6 100644 --- a/test/commerce/orderSubscriptionList.js +++ b/test/commerce/orderSubscriptionList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscription List (정기구독 목록 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // 기본 목록 조회 const response = await commerce.orderSubscription.list() diff --git a/test/commerce/orderSubscriptionPause.js b/test/commerce/orderSubscriptionPause.js index 044c582..f981e3e 100644 --- a/test/commerce/orderSubscriptionPause.js +++ b/test/commerce/orderSubscriptionPause.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscription Pause (정기구독 일시정지) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscription.requestIng.pause({ order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', diff --git a/test/commerce/orderSubscriptionRequestList.js b/test/commerce/orderSubscriptionRequestList.js index d28f792..e31e835 100644 --- a/test/commerce/orderSubscriptionRequestList.js +++ b/test/commerce/orderSubscriptionRequestList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscription Request 조회 (본인 모드) (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscriptionRequest.list({ page: 1, limit: 10 }) console.log('OrderSubscription Request List:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/orderSubscriptionResume.js b/test/commerce/orderSubscriptionResume.js index 6b370c4..395243a 100644 --- a/test/commerce/orderSubscriptionResume.js +++ b/test/commerce/orderSubscriptionResume.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscription Resume (정기구독 재개) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscription.requestIng.resume({ order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE' diff --git a/test/commerce/orderSubscriptionTermination.js b/test/commerce/orderSubscriptionTermination.js index c213a3a..93d091e 100644 --- a/test/commerce/orderSubscriptionTermination.js +++ b/test/commerce/orderSubscriptionTermination.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscription Termination (정기구독 해지) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscription.requestIng.termination({ order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', diff --git a/test/commerce/orderSubscriptionUpdate.js b/test/commerce/orderSubscriptionUpdate.js index 5246b69..0a8a62b 100644 --- a/test/commerce/orderSubscriptionUpdate.js +++ b/test/commerce/orderSubscriptionUpdate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - OrderSubscription Update (정기구독 수정) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.orderSubscription.update({ order_subscription_id: 'ORDER_SUBSCRIPTION_ID_HERE', diff --git a/test/commerce/pointBalance.js b/test/commerce/pointBalance.js index 2c37987..41647ef 100644 --- a/test/commerce/pointBalance.js +++ b/test/commerce/pointBalance.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - 적립금 잔액 조회 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.point.balance() console.log('Point Balance:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/pointCalculateLimit.js b/test/commerce/pointCalculateLimit.js index cc59f00..357c9b7 100644 --- a/test/commerce/pointCalculateLimit.js +++ b/test/commerce/pointCalculateLimit.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - 적립금 사용 한도 계산 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.point.calculateLimit({ order_total: 10000 }) console.log('Point Calculate Limit:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/pointPreviewUsage.js b/test/commerce/pointPreviewUsage.js index 0bba16c..bbed90c 100644 --- a/test/commerce/pointPreviewUsage.js +++ b/test/commerce/pointPreviewUsage.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - 적립금 사용 미리보기 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.point.previewUsage({ amount: 1000, order_total: 10000 diff --git a/test/commerce/pointTransactions.js b/test/commerce/pointTransactions.js index ea2514d..8f420cb 100644 --- a/test/commerce/pointTransactions.js +++ b/test/commerce/pointTransactions.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - 적립금 내역 조회 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.point.transactions({ page: 1, limit: 10 }) console.log('Point Transactions:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/productCreate.js b/test/commerce/productCreate.js index 3414d83..debdad2 100644 --- a/test/commerce/productCreate.js +++ b/test/commerce/productCreate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Product Create (상품 생성) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // 이미지 없이 상품 생성 const response = await commerce.product.create({ diff --git a/test/commerce/productDelete.js b/test/commerce/productDelete.js index e55da33..dd19a7d 100644 --- a/test/commerce/productDelete.js +++ b/test/commerce/productDelete.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Product Delete (상품 삭제) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.product.delete('PRODUCT_ID_HERE') console.log('Product Delete Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/productDetail.js b/test/commerce/productDetail.js index a9c61fe..2e2b2ed 100644 --- a/test/commerce/productDetail.js +++ b/test/commerce/productDetail.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Product Detail (상품 상세 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.product.detail('PRODUCT_ID_HERE') console.log('Product Detail Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/productList.js b/test/commerce/productList.js index 8489668..de23c4c 100644 --- a/test/commerce/productList.js +++ b/test/commerce/productList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Product List (상품 목록 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // 기본 목록 조회 const response = await commerce.product.list() diff --git a/test/commerce/productStatus.js b/test/commerce/productStatus.js index 2dcbf10..8f7da2f 100644 --- a/test/commerce/productStatus.js +++ b/test/commerce/productStatus.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Product Status (상품 상태 변경) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.product.status({ product_id: 'PRODUCT_ID_HERE', diff --git a/test/commerce/productUpdate.js b/test/commerce/productUpdate.js index e548ce6..785e7ef 100644 --- a/test/commerce/productUpdate.js +++ b/test/commerce/productUpdate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - Product Update (상품 수정) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.product.update({ product_id: 'PRODUCT_ID_HERE', diff --git a/test/commerce/userAuthenticationData.js b/test/commerce/userAuthenticationData.js index ae367c4..e6ab316 100644 --- a/test/commerce/userAuthenticationData.js +++ b/test/commerce/userAuthenticationData.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - User Authentication Data (본인인증 데이터 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.user.authenticationData('STAND_ID_HERE') console.log('User Authentication Data Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/userCheckExist.js b/test/commerce/userCheckExist.js index 8f05188..b1d4b56 100644 --- a/test/commerce/userCheckExist.js +++ b/test/commerce/userCheckExist.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - User Check Exist (중복 체크) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // login_id 중복 체크 const loginIdCheck = await commerce.user.checkExist('login_id', 'test_user@example.com') diff --git a/test/commerce/userDelete.js b/test/commerce/userDelete.js index bfd4843..3fea90b 100644 --- a/test/commerce/userDelete.js +++ b/test/commerce/userDelete.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - User Delete (회원탈퇴) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.user.delete('USER_ID_HERE') console.log('User Delete Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/userDetail.js b/test/commerce/userDetail.js index 98d819c..fe52f39 100644 --- a/test/commerce/userDetail.js +++ b/test/commerce/userDetail.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - User Detail (사용자 상세 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.user.detail('USER_ID_HERE') console.log('User Detail Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/userGroupAggregateTransaction.js b/test/commerce/userGroupAggregateTransaction.js index 7105344..3bb1c29 100644 --- a/test/commerce/userGroupAggregateTransaction.js +++ b/test/commerce/userGroupAggregateTransaction.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - UserGroup Aggregate Transaction (그룹 거래 집계 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.userGroup.aggregateTransaction({ user_group_id: 'USER_GROUP_ID_HERE', diff --git a/test/commerce/userGroupCreate.js b/test/commerce/userGroupCreate.js index ef0d857..c2ee59a 100644 --- a/test/commerce/userGroupCreate.js +++ b/test/commerce/userGroupCreate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - UserGroup Create (사용자 그룹 생성) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.userGroup.create({ name: '테스트 그룹', diff --git a/test/commerce/userGroupDetail.js b/test/commerce/userGroupDetail.js index c338501..af347dc 100644 --- a/test/commerce/userGroupDetail.js +++ b/test/commerce/userGroupDetail.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - UserGroup Detail (사용자 그룹 상세 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.userGroup.detail('USER_GROUP_ID_HERE') console.log('UserGroup Detail Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/userGroupLimit.js b/test/commerce/userGroupLimit.js index 2006d6a..d59120c 100644 --- a/test/commerce/userGroupLimit.js +++ b/test/commerce/userGroupLimit.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - UserGroup Limit (그룹 제한 설정) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.userGroup.limit({ user_group_id: 'USER_GROUP_ID_HERE', diff --git a/test/commerce/userGroupList.js b/test/commerce/userGroupList.js index 25d8576..65d7523 100644 --- a/test/commerce/userGroupList.js +++ b/test/commerce/userGroupList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - UserGroup List (사용자 그룹 목록 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // 기본 목록 조회 const response = await commerce.userGroup.list() diff --git a/test/commerce/userGroupUpdate.js b/test/commerce/userGroupUpdate.js index 91b7b15..32b6d0a 100644 --- a/test/commerce/userGroupUpdate.js +++ b/test/commerce/userGroupUpdate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - UserGroup Update (사용자 그룹 수정) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.userGroup.update({ user_group_id: 'USER_GROUP_ID_HERE', diff --git a/test/commerce/userGroupUserCreate.js b/test/commerce/userGroupUserCreate.js index d5394d6..456e745 100644 --- a/test/commerce/userGroupUserCreate.js +++ b/test/commerce/userGroupUserCreate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - UserGroup User Create (그룹에 사용자 추가) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.userGroup.userCreate( 'USER_GROUP_ID_HERE', diff --git a/test/commerce/userGroupUserDelete.js b/test/commerce/userGroupUserDelete.js index 6d472b8..ab5fb25 100644 --- a/test/commerce/userGroupUserDelete.js +++ b/test/commerce/userGroupUserDelete.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - UserGroup User Delete (그룹에서 사용자 제거) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.userGroup.userDelete( 'USER_GROUP_ID_HERE', diff --git a/test/commerce/userJoin.js b/test/commerce/userJoin.js index 8d027c7..42f33f8 100644 --- a/test/commerce/userJoin.js +++ b/test/commerce/userJoin.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - User Join (회원가입) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.user.join({ login_id: 'test_user@example.com', diff --git a/test/commerce/userList.js b/test/commerce/userList.js index 4c55018..3c62afc 100644 --- a/test/commerce/userList.js +++ b/test/commerce/userList.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - User List (사용자 목록 조회) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() // 기본 목록 조회 const response = await commerce.user.list() diff --git a/test/commerce/userLogin.js b/test/commerce/userLogin.js index e22f038..f78a3f0 100644 --- a/test/commerce/userLogin.js +++ b/test/commerce/userLogin.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - User Login 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.user.login('test_user@example.com', 'password123') console.log('User Login Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/userToken.js b/test/commerce/userToken.js index 29716b2..290f839 100644 --- a/test/commerce/userToken.js +++ b/test/commerce/userToken.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - User Token 발급 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.user.token('USER_ID_HERE') console.log('User Token Response:', JSON.stringify(response, null, 2)) diff --git a/test/commerce/userUpdate.js b/test/commerce/userUpdate.js index fa09fff..1322759 100644 --- a/test/commerce/userUpdate.js +++ b/test/commerce/userUpdate.js @@ -1,16 +1,19 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); // Commerce API - User Update (사용자 정보 수정) 테스트 (async () => { const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') const commerce = new BootpayCommerce({ - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=', - mode: 'development' + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode }) try { - await commerce.getAccessToken() + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() const response = await commerce.user.update({ user_id: 'USER_ID_HERE', diff --git a/test/config.js b/test/config.js index 72085d9..bcd0201 100644 --- a/test/config.js +++ b/test/config.js @@ -1,31 +1,82 @@ /** * SDK 테스트용 설정 파일 */ +const fs = require('fs'); +const path = require('path'); + +function loadDotEnv() { + const candidates = [ + path.resolve(__dirname, '..', '.env'), + path.resolve(__dirname, '.env') + ]; + for (const file of candidates) { + if (!fs.existsSync(file)) continue; + const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/); + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + const idx = trimmed.indexOf('='); + if (idx < 0) continue; + const key = trimmed.slice(0, idx).trim(); + let value = trimmed.slice(idx + 1).trim(); + if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + if (process.env[key] === undefined) process.env[key] = value; + } + } +} + +loadDotEnv(); + +const env = (key, fallback) => process.env[key] || fallback; // 현재 환경: 'production' 또는 'development' -const CURRENT_ENV = 'production'; +const CURRENT_ENV = env('BOOTPAY_ENV', 'production'); -// PG API 키 +// PG 인증 방식: 'new' (client_key/secret_key) 또는 'legacy' (application_id/private_key) +// 매 실행 시 BOOTPAY_AUTH_MODE 환경변수로 토글한다. +const AUTH_MODE = (env('BOOTPAY_AUTH_MODE', 'new') || 'new').toLowerCase(); + +// PG API 키 (반드시 .env 또는 환경변수로 주입; .env.example 참고) const PG_CREDENTIALS = { production: { - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + mode: 'production', + client_key: env('BOOTPAY_PG_CLIENT_KEY_PROD', ''), + secret_key: env('BOOTPAY_PG_SECRET_KEY_PROD', '') }, development: { - application_id: '59bfc738e13f337dbd6ca48a', - private_key: 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' + mode: 'development', + client_key: env('BOOTPAY_PG_CLIENT_KEY_DEV', ''), + secret_key: env('BOOTPAY_PG_SECRET_KEY_DEV', '') } }; -// Commerce API 키 +// PG API legacy application_id/private_key 인증 (호환성 검증용) +const PG_LEGACY_CREDENTIALS = { + production: { + mode: 'production', + application_id: env('BOOTPAY_PG_APPLICATION_ID_PROD', ''), + private_key: env('BOOTPAY_PG_PRIVATE_KEY_PROD', '') + }, + development: { + mode: 'development', + application_id: env('BOOTPAY_PG_APPLICATION_ID_DEV', ''), + private_key: env('BOOTPAY_PG_PRIVATE_KEY_DEV', '') + } +}; + +// Commerce API 키 (반드시 .env 또는 환경변수로 주입; .env.example 참고) const COMMERCE_CREDENTIALS = { production: { - client_key: 'sEN72kYZBiyMNytA8nUGxQ', - secret_key: 'rnZLJamENRgfwTccwmI_Uu9cxsPpAV9X2W-Htg73yfU=' + mode: 'production', + client_key: env('BOOTPAY_COMMERCE_CLIENT_KEY_PROD', ''), + secret_key: env('BOOTPAY_COMMERCE_SECRET_KEY_PROD', '') }, development: { - client_key: 'hxS-Up--5RvT6oU6QJE0JA', - secret_key: 'r5zxvDcQJiAP2PBQ0aJjSHQtblNmYFt6uFoEMhti_mg=' + mode: 'development', + client_key: env('BOOTPAY_COMMERCE_CLIENT_KEY_DEV', ''), + secret_key: env('BOOTPAY_COMMERCE_SECRET_KEY_DEV', '') } }; @@ -33,6 +84,7 @@ const COMMERCE_CREDENTIALS = { const TEST_DATA = { receipt_id: '628b2206d01c7e00209b6087', receipt_id_confirm: '62876963d01c7e00209b6028', + // receipt_id_confirm: '69fd7187564d1f550535538c', receipt_id_cash: '62e0f11f1fc192036b1b3c92', receipt_id_escrow: '628ae7ffd01c7e001e9b6066', receipt_id_billing: '62c7ccebcf9f6d001b3adcd4', @@ -42,24 +94,49 @@ const TEST_DATA = { reserve_id: '6490149ca575b40024f0b70d', reserve_id_2: '628b316cd01c7e00219b6081', user_id: '1234', - certificate_receipt_id: '61b009aaec81b4057e7f6ecd' + certificate_receipt_id: '69fd7187564d1f550535538c' }; -// PG API 키 가져오기 -function getPgKeys() { - return PG_CREDENTIALS[CURRENT_ENV]; +function normalizeEnv(targetEnv) { + return targetEnv || CURRENT_ENV; } -// Commerce API 키 가져오기 -function getCommerceKeys() { - return COMMERCE_CREDENTIALS[CURRENT_ENV]; +function getPgKeys(targetEnv) { + return PG_CREDENTIALS[normalizeEnv(targetEnv)] || PG_CREDENTIALS.production; +} + +function getPgLegacyKeys(targetEnv) { + return PG_LEGACY_CREDENTIALS[normalizeEnv(targetEnv)] || PG_LEGACY_CREDENTIALS.production; +} + +function getCommerceKeys(targetEnv) { + return COMMERCE_CREDENTIALS[normalizeEnv(targetEnv)] || COMMERCE_CREDENTIALS.production; +} + +// AUTH_MODE 에 따라 setConfiguration 에 그대로 넘길 수 있는 PG config 를 반환한다. +// BOOTPAY_AUTH_MODE=new → { client_key, secret_key, mode } +// BOOTPAY_AUTH_MODE=legacy → { application_id, private_key, mode } +function getActivePgConfig(targetEnv) { + const env = normalizeEnv(targetEnv); + if (AUTH_MODE === 'legacy') { + console.log(`[BOOTPAY_AUTH_MODE=legacy] PG: application_id/private_key (Bearer) | env=${env}`); + const k = getPgLegacyKeys(env); + return { application_id: k.application_id, private_key: k.private_key, mode: k.mode }; + } + console.log(`[BOOTPAY_AUTH_MODE=new] PG: client_key/secret_key (Basic Auth) | env=${env}`); + const k = getPgKeys(env); + return { client_key: k.client_key, secret_key: k.secret_key, mode: k.mode }; } module.exports = { CURRENT_ENV, + AUTH_MODE, PG_CREDENTIALS, + PG_LEGACY_CREDENTIALS, COMMERCE_CREDENTIALS, TEST_DATA, getPgKeys, + getPgLegacyKeys, + getActivePgConfig, getCommerceKeys }; diff --git a/test/confirmPayment.js b/test/confirmPayment.js deleted file mode 100644 index 91ba15a..0000000 --- a/test/confirmPayment.js +++ /dev/null @@ -1,15 +0,0 @@ -// import { Bootpay } from "../dist/bootpay" -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.confirmPayment('62876963d01c7e00209b6028') - console.log(response) - } catch (e) { - console.log(e) - } -})() diff --git a/test/destroySubscribeBillingKey.js b/test/destroySubscribeBillingKey.js deleted file mode 100644 index e1e58a5..0000000 --- a/test/destroySubscribeBillingKey.js +++ /dev/null @@ -1,14 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.destroyBillingKey('62b3d166cf9f6d001bd20d59') - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/getAccessToken.js b/test/getAccessToken.js deleted file mode 100644 index b106c65..0000000 --- a/test/getAccessToken.js +++ /dev/null @@ -1,16 +0,0 @@ -// import { Bootpay } from "../dist/index.js"; -// - -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - let response = await Bootpay.getAccessToken() - console.log(response) - } catch (e) { - console.log(e) - } -})() diff --git a/test/getUserWallets.js b/test/getUserWallets.js deleted file mode 100644 index d8da56a..0000000 --- a/test/getUserWallets.js +++ /dev/null @@ -1,17 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.getUserWallets( - 'bootpay', - true - ) - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/lookupBilling.js b/test/lookupBilling.js deleted file mode 100644 index f0d46d8..0000000 --- a/test/lookupBilling.js +++ /dev/null @@ -1,34 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.lookupBillingKey('66542dfb4d18d5fc7b43e1b6') - console.log(response) - } catch (e) { - console.log(e) - } -})() - -/* -{ - billing_key: '66542dfb4d18d5fc7b43e1b6', - pg: '나이스페이먼츠', - method: '계좌자동이체', - method_symbol: 'automatic_transfer_rest', - billing_data: { - bank_name: '국민', - bank_code: '004', - bank_account: '0000000000000000', - username: '윤태*' - }, - version: 2, - sandbox: 1, - expire_at: '2099-12-31T23:59:59+09:00', - published_at: '2024-05-27T15:53:47+09:00', - status: 1 -} - */ \ No newline at end of file diff --git a/test/lookupSubscribeBilling.js b/test/lookupSubscribeBilling.js deleted file mode 100644 index c904279..0000000 --- a/test/lookupSubscribeBilling.js +++ /dev/null @@ -1,14 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '6784d481a3175898bd6e5494', - private_key: 'cznW8pZU8f60PAT2p/VUyJidiz0PdKXiro2LikyZnH4=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.lookupSubscribeBillingKey('67a1faaf54c6b5ba3bc0b98d') - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/pg/authenticateConfirmRest.mjs b/test/pg/authenticateConfirmRest.mjs new file mode 100644 index 0000000..88061be --- /dev/null +++ b/test/pg/authenticateConfirmRest.mjs @@ -0,0 +1,18 @@ +import config from '../config.js'; +const { getActivePgConfig, TEST_DATA } = config; +import { Bootpay } from "../../dist/bootpay.js" + +(async () => { + Bootpay.setConfiguration(getActivePgConfig('production')) + try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. + await Bootpay.getAccessToken() + const response = await Bootpay.confirmAuthentication( + TEST_DATA.receipt_id_confirm, + '457670' + ) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/pg/authenticateRealarmRest.js b/test/pg/authenticateRealarmRest.js new file mode 100644 index 0000000..581b5d3 --- /dev/null +++ b/test/pg/authenticateRealarmRest.js @@ -0,0 +1,15 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); + +(async () => { + Bootpay.setConfiguration(getActivePgConfig('production')) + try { + // console.log(new Date((new Date()).getTime() + 5000)) + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. + await Bootpay.getAccessToken() + const response = await Bootpay.realarmAuthentication(TEST_DATA.receipt_id_confirm) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/authenticateRequestRest.js b/test/pg/authenticateRequestRest.js similarity index 68% rename from test/authenticateRequestRest.js rename to test/pg/authenticateRequestRest.js index 0d8d3af..fd14530 100644 --- a/test/authenticateRequestRest.js +++ b/test/pg/authenticateRequestRest.js @@ -1,10 +1,10 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getActivePgConfig } = require('../config.js'); + (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) + Bootpay.setConfiguration(getActivePgConfig('production')) try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken() const response = await Bootpay.requestAuthentication({ pg: '다날', diff --git a/test/pg/axios_test.js b/test/pg/axios_test.js new file mode 100644 index 0000000..465360d --- /dev/null +++ b/test/pg/axios_test.js @@ -0,0 +1,27 @@ +const { getPgKeys } = require('../config.js'); +const keys = getPgKeys('production'); + +(async () => { + const RestClient = require('../../dist/bootpay').Bootpay + RestClient.setConfiguration({ + client_key: keys.client_key, + secret_key: keys.secret_key, + }) + // Legacy fallback: + // RestClient.setConfiguration({ + // application_id: '5b8f6a4d396fa665fdc2b5ea', + // private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' + // }) + let response = await RestClient.getAccessToken() + // console.log(response) + const axios = require('axios') + try { + response = await axios.post("https://dev-api.bootpay.co.kr/request/token.json", { + client_key: keys.client_key, + secret_key: keys.secret_key, + }) + console.log(response) + } catch(e) { + console.log(e.response.data) + } +})() diff --git a/test/pg/cancelPayment.js b/test/pg/cancelPayment.js index bbca249..0241d97 100644 --- a/test/pg/cancelPayment.js +++ b/test/pg/cancelPayment.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.cancelPayment({ receipt_id: TEST_DATA.receipt_id, diff --git a/test/pg/cancelSubscribeReserve.js b/test/pg/cancelSubscribeReserve.js index 63ff1ea..a88133f 100644 --- a/test/pg/cancelSubscribeReserve.js +++ b/test/pg/cancelSubscribeReserve.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); // 예약 결제 등록 const reserve = await Bootpay.subscribePaymentReserve({ diff --git a/test/pg/cashReceiptPublishOnReceipt.js b/test/pg/cashReceiptPublishOnReceipt.js index 4314c43..51731ea 100644 --- a/test/pg/cashReceiptPublishOnReceipt.js +++ b/test/pg/cashReceiptPublishOnReceipt.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.cashReceiptPublishOnReceipt({ receipt_id: TEST_DATA.receipt_id_cash, diff --git a/test/pg/certificate.js b/test/pg/certificate.js index 2c2e831..6ccf857 100644 --- a/test/pg/certificate.js +++ b/test/pg/certificate.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.certificate(TEST_DATA.certificate_receipt_id); console.log(response); diff --git a/test/pg/confirmPayment.js b/test/pg/confirmPayment.js index 66b97e3..da59e9a 100644 --- a/test/pg/confirmPayment.js +++ b/test/pg/confirmPayment.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.confirmPayment(TEST_DATA.receipt_id_confirm); console.log(response); diff --git a/test/pg/destroySubscribeBillingKey.js b/test/pg/destroySubscribeBillingKey.js index 21cad19..ba91480 100644 --- a/test/pg/destroySubscribeBillingKey.js +++ b/test/pg/destroySubscribeBillingKey.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.destroySubscribeBillingKey(TEST_DATA.billing_key); console.log(response); diff --git a/test/form_payment_progress.js b/test/pg/form_payment_progress.js similarity index 72% rename from test/form_payment_progress.js rename to test/pg/form_payment_progress.js index 8b1669b..22ad367 100644 --- a/test/form_payment_progress.js +++ b/test/pg/form_payment_progress.js @@ -1,9 +1,17 @@ -const Bootpay = require('../lib/bootpay'); +// (legacy) 이 파일은 레거시 application_id/private_key + Promise then() 체인 예제다. +// ck/sk 모드에서는 getAccessToken() 호출이 불필요하며, 매 요청 Basic Auth 헤더로 +// 직접 인증된다. 신규 코드는 await 패턴으로 토큰 호출 없이 바로 결제/조회 API를 호출하면 된다. +const { Bootpay } = require('../../dist/bootpay.js'); -Bootpay.setConfig( - "[[ REST용 Application ID]]", - "[[ Private Key ]]" -); +Bootpay.setConfiguration({ + client_key: '[[ Client Key ]]', + secret_key: '[[ Server Key ]]' +}); +// Legacy fallback: +// Bootpay.setConfiguration({ +// application_id: '[[ REST용 Application ID ]]', +// private_key: '[[ Private Key ]]' +// }); // POST로 Params를 받아서 처리 // params가 POST로 전달된 Object라고 가정하면 @@ -42,4 +50,4 @@ switch (params.act) { }); break; -} \ No newline at end of file +} diff --git a/test/pg/getAccessToken.js b/test/pg/getAccessToken.js index e592751..2d1a5b9 100644 --- a/test/pg/getAccessToken.js +++ b/test/pg/getAccessToken.js @@ -1,16 +1,38 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getPgKeys, getPgLegacyKeys } = require('../config.js'); (async () => { - const keys = getPgKeys(); + // 1) client_key/secret_key 경로 — Option A no-op 검증 (HTTP 호출 없이 합성 응답) + const ck = getPgKeys(); Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key + client_key: ck.client_key, + secret_key: ck.secret_key, + mode: ck.mode }); try { const response = await Bootpay.getAccessToken(); - console.log(response); + console.log('[ck/sk]', response); + if (response.access_token !== '' || response.expire_in !== 0) { + console.error('[ck/sk] expected synthetic empty response'); + } } catch (e) { - console.log(e); + console.log('[ck/sk] ERR', e); + } + + // 2) legacy application_id/private_key 경로 — 실제 request/token 호출 + Bearer 토큰 발급 검증 + const legacy = getPgLegacyKeys(); + Bootpay.setConfiguration({ + application_id: legacy.application_id, + private_key: legacy.private_key, + mode: legacy.mode + }); + try { + const response = await Bootpay.getAccessToken(); + console.log('[legacy]', response); + if (!response.access_token || response.expire_in <= 0) { + console.error('[legacy] expected real access_token + positive expire_in'); + } + } catch (e) { + console.log('[legacy] ERR', e); } })(); diff --git a/test/getBillingKey.js b/test/pg/getBillingKey.js similarity index 75% rename from test/getBillingKey.js rename to test/pg/getBillingKey.js index 06f2707..7189d11 100644 --- a/test/getBillingKey.js +++ b/test/pg/getBillingKey.js @@ -1,10 +1,10 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getActivePgConfig } = require('../config.js'); + (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) + Bootpay.setConfiguration(getActivePgConfig('production')) try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribeBillingKey({ pg: '나이스페이', diff --git a/test/pg/getUserWallets.js b/test/pg/getUserWallets.js new file mode 100644 index 0000000..c010ede --- /dev/null +++ b/test/pg/getUserWallets.js @@ -0,0 +1,17 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getActivePgConfig } = require('../config.js'); + +(async () => { + Bootpay.setConfiguration(getActivePgConfig('production')) + try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. + await Bootpay.getAccessToken() + const response = await Bootpay.getUserWallets( + 'bootpay', + true + ) + console.log(response) + } catch (e) { + console.log(e) + } +})() \ No newline at end of file diff --git a/test/pg/legacyCompatibility.js b/test/pg/legacyCompatibility.js new file mode 100644 index 0000000..1e452bd --- /dev/null +++ b/test/pg/legacyCompatibility.js @@ -0,0 +1,101 @@ +const assert = require('assert'); +const { Bootpay } = require('../../dist/bootpay.js'); + +function header(config, name) { + if (!config.headers) return undefined; + if (typeof config.headers.get === 'function') return config.headers.get(name); + return config.headers[name] || config.headers[name.toLowerCase()] || config.headers[name.toUpperCase()]; +} + +function body(data) { + return typeof data === 'string' ? JSON.parse(data) : data; +} + +(async () => { + const requests = []; + Bootpay.$http.defaults.adapter = async (config) => { + requests.push(config); + return { + data: { access_token: 'legacy_access_token' }, + status: 200, + statusText: 'OK', + headers: {}, + config, + request: {} + }; + }; + + Bootpay.setConfiguration({ + application_id: 'legacy_application_id', + private_key: 'legacy_private_key', + mode: 'development' + }); + + assert.strictEqual(Bootpay.bootpayConfiguration.application_id, 'legacy_application_id'); + assert.strictEqual(Bootpay.bootpayConfiguration.private_key, 'legacy_private_key'); + assert.strictEqual(Bootpay.bootpayConfiguration.client_key, undefined); + assert.strictEqual(Bootpay.bootpayConfiguration.secret_key, undefined); + assert.strictEqual(Bootpay.entrypoints('request/token'), 'https://dev-api.bootpay.co.kr/v2/request/token'); + + // 1) legacy: getAccessToken 은 request/token 으로 토큰 발급 + const legacyTokenRes = await Bootpay.getAccessToken(); + assert.strictEqual(legacyTokenRes.access_token, 'legacy_access_token'); + assert.deepStrictEqual(body(requests[0].data), { + application_id: 'legacy_application_id', + private_key: 'legacy_private_key' + }); + assert.strictEqual(header(requests[0], 'authorization'), undefined); + + // 2) legacy: 후속 요청에 Bearer 헤더 자동 부착 + await Bootpay.receiptPayment('receipt_id_for_header_check'); + assert.strictEqual(header(requests[1], 'authorization'), 'Bearer legacy_access_token'); + + // 3) ck/sk: getAccessToken 은 Option A 로 no-op — HTTP 호출 없이 합성 응답만 반환 + Bootpay.setConfiguration({ + client_key: 'ck', + secret_key: 'sk', + mode: 'development' + }); + const beforeCk = requests.length; + const ckTokenRes = await Bootpay.getAccessToken(); + assert.deepStrictEqual(ckTokenRes, { access_token: '', expire_in: 0 }); + assert.strictEqual(requests.length, beforeCk, 'ck/sk getAccessToken must not make HTTP call'); + + // 4) ck/sk: 실제 API 요청 시 매 요청 Basic Auth 헤더 부착 + await Bootpay.receiptPayment('receipt_id_for_basic_auth_check'); + assert.strictEqual( + header(requests[beforeCk], 'authorization'), + `Basic ${Buffer.from('ck:sk').toString('base64')}` + ); + + // 5) ck 만 있고 sk 없으면 즉시 에러 + Bootpay.setConfiguration({ + client_key: 'ck', + mode: 'production' + }); + await assert.rejects( + () => Bootpay.getAccessToken(), + (error) => error.error_code === -101 && error.message.includes('client_key/secret_key') + ); + + // 6) legacy + 부주의하게 sk 만 함께 들어와도 ck 없으면 legacy 경로 유지 + Bootpay.setConfiguration({ + application_id: 'legacy_application_id', + private_key: 'legacy_private_key', + secret_key: 'ignored_without_client_key', + mode: 'production' + }); + Bootpay.$token = undefined; + const beforeFallback = requests.length; + await Bootpay.getAccessToken(); + assert.deepStrictEqual(body(requests[beforeFallback].data), { + application_id: 'legacy_application_id', + private_key: 'legacy_private_key' + }); + assert.strictEqual(header(requests[beforeFallback], 'authorization'), undefined); + + console.log('legacy application_id/private_key and client_key/secret_key auth are compatible'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/pg/lookupBilling.js b/test/pg/lookupBilling.js index 9fdb69d..09dbeb7 100644 --- a/test/pg/lookupBilling.js +++ b/test/pg/lookupBilling.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.lookupBillingKey(TEST_DATA.billing_key_2); console.log(response); diff --git a/test/pg/lookupSubscribeBilling.js b/test/pg/lookupSubscribeBilling.js index a80bf15..ba2996d 100644 --- a/test/pg/lookupSubscribeBilling.js +++ b/test/pg/lookupSubscribeBilling.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.lookupSubscribeBillingKey(TEST_DATA.receipt_id_billing); console.log(response); diff --git a/test/publishAutomaticTransferBillingKey.js b/test/pg/publishAutomaticTransferBillingKey.js similarity index 86% rename from test/publishAutomaticTransferBillingKey.js rename to test/pg/publishAutomaticTransferBillingKey.js index de4ab18..16fef23 100644 --- a/test/publishAutomaticTransferBillingKey.js +++ b/test/pg/publishAutomaticTransferBillingKey.js @@ -1,12 +1,12 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); + (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) + Bootpay.setConfiguration(getActivePgConfig('production')) try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken() - const response = await Bootpay.publishAutomaticTransferBillingKey('6655069ca691573f1bb9c28a') + const response = await Bootpay.publishAutomaticTransferBillingKey(TEST_DATA.receipt_id_transfer) console.log(response) } catch (e) { console.log(e) diff --git a/test/pg/receiptPayment.js b/test/pg/receiptPayment.js index f57354e..1ce9d19 100644 --- a/test/pg/receiptPayment.js +++ b/test/pg/receiptPayment.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.receiptPayment(TEST_DATA.receipt_id); console.log(response); diff --git a/test/requestCashReceipt.js b/test/pg/requestCashReceipt.js similarity index 52% rename from test/requestCashReceipt.js rename to test/pg/requestCashReceipt.js index b85c13f..f51573d 100644 --- a/test/requestCashReceipt.js +++ b/test/pg/requestCashReceipt.js @@ -1,17 +1,11 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getActivePgConfig } = require('../config.js'); + (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - // Bootpay.setConfiguration({ - // application_id: '59bfc738e13f337dbd6ca48a', - // private_key: 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', - // mode: 'development' - // }) + Bootpay.setConfiguration(getActivePgConfig('production')); try { - // console.log(new Date((new Date()).getTime() + 5000)) - await Bootpay.getAccessToken() + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. + await Bootpay.getAccessToken(); const response = await Bootpay.requestCashReceipt({ pg: '나이스페이', price: 1000, @@ -25,15 +19,15 @@ }, identity_no: '0100000000', order_id: (new Date()).getTime(), - }) - console.log(response) + }); + console.log(response); if (response.receipt_id !== undefined) { const cancel = await Bootpay.cancelCashReceipt({ receipt_id: response.receipt_id, - }) - console.log(cancel) + }); + console.log(cancel); } } catch (e) { - console.log(e) + console.log(e); } -})() \ No newline at end of file +})(); diff --git a/test/requestSubscribeAutomaticTransferBillingKey.js b/test/pg/requestSubscribeAutomaticTransferBillingKey.js similarity index 84% rename from test/requestSubscribeAutomaticTransferBillingKey.js rename to test/pg/requestSubscribeAutomaticTransferBillingKey.js index fac5caa..ce24aef 100644 --- a/test/requestSubscribeAutomaticTransferBillingKey.js +++ b/test/pg/requestSubscribeAutomaticTransferBillingKey.js @@ -1,10 +1,10 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getActivePgConfig } = require('../config.js'); + (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) + Bootpay.setConfiguration(getActivePgConfig('production')) try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribeAutomaticTransferBillingKey({ pg: '나이스페이', diff --git a/test/pg/requestUserToken.js b/test/pg/requestUserToken.js index 37a5aff..4d622e2 100644 --- a/test/pg/requestUserToken.js +++ b/test/pg/requestUserToken.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.requestUserToken({ user_id: TEST_DATA.user_id diff --git a/test/request_payment.js b/test/pg/request_payment.js similarity index 93% rename from test/request_payment.js rename to test/pg/request_payment.js index 8d8a738..34db78a 100644 --- a/test/request_payment.js +++ b/test/pg/request_payment.js @@ -1,6 +1,6 @@ // @deprecated // (async () => { -// const Bootpay = require('../dist/bootpay').Bootpay +// const Bootpay = require('../../dist/bootpay').Bootpay // Bootpay.setConfig( // '5b8f6a4d396fa665fdc2b5ea', // 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' diff --git a/test/pg/shippingStart.js b/test/pg/shippingStart.js index c291f23..243d1e1 100644 --- a/test/pg/shippingStart.js +++ b/test/pg/shippingStart.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.shippingStart({ receipt_id: TEST_DATA.receipt_id_escrow, diff --git a/test/pg/subscribeCardPayment.js b/test/pg/subscribeCardPayment.js index ca620b6..012e235 100644 --- a/test/pg/subscribeCardPayment.js +++ b/test/pg/subscribeCardPayment.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.requestSubscribeCardPayment({ billing_key: TEST_DATA.billing_key, diff --git a/test/subscribePayment.js b/test/pg/subscribePayment.js similarity index 53% rename from test/subscribePayment.js rename to test/pg/subscribePayment.js index 436d99f..d78e99a 100644 --- a/test/subscribePayment.js +++ b/test/pg/subscribePayment.js @@ -1,13 +1,13 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); + (async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) + Bootpay.setConfiguration(getActivePgConfig('production')) try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken() const response = await Bootpay.requestSubscribePayment({ - billing_key: '62b3d166cf9f6d001bd20d59', + billing_key: TEST_DATA.billing_key, order_name: '테스트 결제', order_id: (new Date()).getTime(), price: 100, diff --git a/test/pg/subscribePaymentReserve.js b/test/pg/subscribePaymentReserve.js index b3cd9d8..6f18bbe 100644 --- a/test/pg/subscribePaymentReserve.js +++ b/test/pg/subscribePaymentReserve.js @@ -1,13 +1,10 @@ const { Bootpay } = require('../../dist/bootpay.js'); -const { getPgKeys, TEST_DATA } = require('../config.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); (async () => { - const keys = getPgKeys(); - Bootpay.setConfiguration({ - application_id: keys.application_id, - private_key: keys.private_key - }); + Bootpay.setConfiguration(getActivePgConfig()); try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); const response = await Bootpay.subscribePaymentReserve({ billing_key: TEST_DATA.billing_key, diff --git a/test/receiptPayment.js b/test/receiptPayment.js deleted file mode 100644 index fb1f6ea..0000000 --- a/test/receiptPayment.js +++ /dev/null @@ -1,16 +0,0 @@ -// import { Bootpay } from "./" - -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.receiptPayment('62b12f4b6262500007629fec') - console.log(response) - } catch (e) { - console.log(e) - } -})() diff --git a/test/requestUserToken.js b/test/requestUserToken.js deleted file mode 100644 index d39d33a..0000000 --- a/test/requestUserToken.js +++ /dev/null @@ -1,17 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.requestUserToken({ - user_id: 'gosomi1', - phone:'01012345678' - }) - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/shippingStart.js b/test/shippingStart.js deleted file mode 100644 index 216a161..0000000 --- a/test/shippingStart.js +++ /dev/null @@ -1,24 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.shippingStart({ - receipt_id: "62a9379ad01c7e001f7dc1f3", - tracking_number: '123456', - delivery_corp: 'CJ대한통운', - user: { - username: '테스트', - phone: '01000000000', - address: '서울특별시 종로구', - zipcode: '08490' - } - }) - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/subscribeCardPayment.js b/test/subscribeCardPayment.js deleted file mode 100644 index a7d1a90..0000000 --- a/test/subscribeCardPayment.js +++ /dev/null @@ -1,20 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.requestSubscribeCardPayment({ - billing_key: '62b3d166cf9f6d001bd20d59', - order_name: '테스트 결제', - order_id: (new Date()).getTime(), - price: 100, - tax_free: 0 - }) - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/subscribePaymentReserve.js b/test/subscribePaymentReserve.js deleted file mode 100644 index ba0f2fc..0000000 --- a/test/subscribePaymentReserve.js +++ /dev/null @@ -1,22 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - // console.log(new Date((new Date()).getTime() + 5000)) - await Bootpay.getAccessToken() - const response = await Bootpay.subscribePaymentReserve({ - billing_key: '62b3d166cf9f6d001bd20d59', - order_name: '테스트 결제', - order_id: (new Date()).getTime(), - price: 1000, - reserve_execute_at: new Date((new Date()).getTime() + 5000) - }) - - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file diff --git a/test/test.md b/test/test.md index d80e266..7fc96cf 100644 --- a/test/test.md +++ b/test/test.md @@ -104,3 +104,61 @@ test/ │ └── ... └── [기존파일].js # 기존 테스트 파일 (레거시) ``` + +## PG 인증 방식 토글 (BOOTPAY_AUTH_MODE) + +PG 테스트는 기본적으로 신규 `client_key/secret_key` 방식으로 동작한다. 매 실행 시 환경변수로 레거시 `application_id/private_key` 방식으로 전환할 수 있다. + +### 토글 contract + +| `BOOTPAY_AUTH_MODE` | 동작 | +|---|---| +| `new` (기본, 미설정 시 동일) | `client_key` + `secret_key` Basic Auth 로 PG 인스턴스 생성. 토큰 발급 호출 불필요. | +| `legacy` | `application_id` + `private_key` 로 PG 인스턴스 생성. 토큰 발급 호출 후 `Bearer` 헤더 사용. | + +키 값은 모두 `.env` (또는 환경변수) 로 주입한다 — `.env.example` 참고. 토글만 바꾸고 키는 그대로 둬도 된다. + +### 사용법 + +```bash +# (1) 기본 — env var 생략 (= new) +node test/pg/receiptPayment.js + +# (2) 한 번만 legacy 로 전환 +BOOTPAY_AUTH_MODE=legacy node test/pg/receiptPayment.js + +# (3) 셸 세션 동안 legacy 고정 +export BOOTPAY_AUTH_MODE=legacy +node test/pg/receiptPayment.js +node test/pg/cancelPayment.js +unset BOOTPAY_AUTH_MODE # 끝나면 해제 + +# (4) 영구 전환 — .env 의 BOOTPAY_AUTH_MODE 값을 legacy 로 바꾸면 셸 export 없이도 동작 +``` + +### 진입 헬퍼 — 어디서 토글이 흡수되는가 + +`test/config.js` 의 `getActivePgConfig()` 가 `BOOTPAY_AUTH_MODE` 값에 따라 `Bootpay.setConfiguration(...)` 인자 dict 를 반환한다. PG 테스트 파일은 모두 한 줄로 두 모드를 모두 지원한다: + +```js +const { getActivePgConfig } = require('../config.js'); +Bootpay.setConfiguration(getActivePgConfig()); +``` + +두 모드 모두 `Bootpay.getAccessToken()` 호출은 안전하다 (ck/sk 모드에서는 SDK 내부에서 no-op). + +### 실행 시 인증 모드 표시 + +`getActivePgConfig()` 가 호출될 때마다 stdout 에 한 줄로 어떤 모드가 활성화됐는지 표시된다 — 어떤 키 set 으로 실행됐는지 로그에서 즉시 확인 가능: + +``` +[BOOTPAY_AUTH_MODE=new] PG: client_key/secret_key (Basic Auth) | env=production +[BOOTPAY_AUTH_MODE=legacy] PG: application_id/private_key (Bearer) | env=production +``` + +### 토글의 영향을 받지 않는 파일 + +다음은 한 스크립트 안에서 두 모드를 모두 검증하므로 환경변수에 무관하게 동일한 동작을 한다: + +- `test/pg/getAccessToken.js` +- `test/legacyCompatibility.js` diff --git a/test/walletPayment.js b/test/walletPayment.js deleted file mode 100644 index 90beca8..0000000 --- a/test/walletPayment.js +++ /dev/null @@ -1,25 +0,0 @@ -(async () => { - const Bootpay = require('../dist/bootpay.js').Bootpay - Bootpay.setConfiguration({ - application_id: '5b8f6a4d396fa665fdc2b5ea', - private_key: 'rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw=' - }) - try { - await Bootpay.getAccessToken() - const response = await Bootpay.requestWalletPayment({ - user_id: 'bootpay', - order_name: '테스트 결제', - order_id: (new Date()).getTime(), - price: 100, - sandbox: true, - user: { - phone: '01012341234', - username: '홍길동', - email: 'test@bootpay.co.kr' - } - }) - console.log(response) - } catch (e) { - console.log(e) - } -})() \ No newline at end of file From 0f001a0a5f9bf9bc7118e637828923a4359fcf0c Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Fri, 8 May 2026 15:11:36 +0900 Subject: [PATCH 107/117] =?UTF-8?q?docs(changelog):=202.5.0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=82=B4=EC=97=AD=20=EC=83=81=EC=84=B8=20=EA=B8=B0?= =?UTF-8?q?=EC=88=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - client_key/secret_key Basic Auth (PG + Commerce) - Commerce V1 신설 모듈: category, coupon, point, orderSubscriptionRequest, cart - Wallet API + http_status 응답 필드 @deprecated 표시 - 테스트 BOOTPAY_AUTH_MODE 토글 + test/pg 디렉터리 분리 Co-Authored-By: Claude Opus 4.7 (1M context) --- CHNAGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHNAGELOG.md b/CHNAGELOG.md index 7ec9a0b..49d3277 100644 --- a/CHNAGELOG.md +++ b/CHNAGELOG.md @@ -1,5 +1,13 @@ ### 2.5.0 -* client_key, secret_key 추가 및 레거시 application_id, private_key 유지 +* 인증: client_key/secret_key Basic Auth 지원 (PG + Commerce 공통) + - 기존 application_id/private_key Bearer 방식 하위 호환 유지 + - ck/sk 모드에서는 request/token 호출 불필요 (getAccessToken 합성 응답) + - ck 또는 sk 한쪽만 지정 + legacy 키도 없으면 NEED_CLIENT_KEY(-101) reject +* Commerce: V1 신설 모듈 추가 — category, coupon, point, orderSubscriptionRequest, cart + - cart.orderPreview: 권위적 배송비/할인 계산 응답 (guest/member 모드) +* Wallet API (`requestWalletPayment`, `WalletRequestParameters`, `WalletPaymentResponseParameters`) `@deprecated` 표시 — 다음 메이저 버전에서 제거 예정 +* `http_status` 응답 필드 `@deprecated` 표시 — 다음 메이저 버전에서 제거 예정 (성공 여부는 `status` 필드 사용) +* 테스트 인프라: `.env` / `BOOTPAY_AUTH_MODE=new|legacy` 토글로 ck/sk · legacy 양쪽 검증, PG 테스트 디렉터리 분리(`test/pg/`) ### 2.4.1 * Commerce 응답포맷 개선 From c2153c28c1a3efb8bbd645011d75732f5b5f1509 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Fri, 8 May 2026 15:48:05 +0900 Subject: [PATCH 108/117] =?UTF-8?q?fix(test):=20destroySubscribeBillingKey?= =?UTF-8?q?=20=ED=98=B8=EC=B6=9C=EC=9D=84=20destroyBillingKey=20=EB=A1=9C?= =?UTF-8?q?=20=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SDK 메서드명은 destroyBillingKey 인데 테스트가 옛 이름을 써서 TypeError 가 났었다. 양쪽 auth 모드에서 이제 동일한 PG 도메인 에러로 응답. Co-Authored-By: Claude Opus 4.7 (1M context) --- test/pg/destroySubscribeBillingKey.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pg/destroySubscribeBillingKey.js b/test/pg/destroySubscribeBillingKey.js index ba91480..42bec1e 100644 --- a/test/pg/destroySubscribeBillingKey.js +++ b/test/pg/destroySubscribeBillingKey.js @@ -6,7 +6,7 @@ const { getActivePgConfig, TEST_DATA } = require('../config.js'); try { // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); - const response = await Bootpay.destroySubscribeBillingKey(TEST_DATA.billing_key); + const response = await Bootpay.destroyBillingKey(TEST_DATA.billing_key); console.log(response); } catch (e) { console.log(e); From ac7aa30fd0db68ad71fb99272e75574ed46c0d31 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Mon, 11 May 2026 09:09:17 +0900 Subject: [PATCH 109/117] chore: rotate Commerce production key in .env.example Sync to currently-valid Commerce production credentials. Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 3bf1e1c..b648d16 100644 --- a/.env.example +++ b/.env.example @@ -17,7 +17,7 @@ BOOTPAY_PG_APPLICATION_ID_DEV=59bfc738e13f337dbd6ca48a BOOTPAY_PG_PRIVATE_KEY_DEV=pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0= # Commerce API -BOOTPAY_COMMERCE_CLIENT_KEY_PROD=K1Xok7RzFxbT7zMBmiBXNw -BOOTPAY_COMMERCE_SECRET_KEY_PROD=vcd_5OXoQAxTA8JSg2VGaSnwmQPkd8DgQ6xiyL6QkyE= +BOOTPAY_COMMERCE_CLIENT_KEY_PROD=JfF1ML0fWiXwnfpKRGvGOA +BOOTPAY_COMMERCE_SECRET_KEY_PROD=MrNdlu26zkKc1axKJM2rj3DyOwOGJJpMKB9RxIKf0Pg= BOOTPAY_COMMERCE_CLIENT_KEY_DEV=ZYEi9d93uIaQFEuxXEZfyQ BOOTPAY_COMMERCE_SECRET_KEY_DEV=j8ONDlZQVHgAWq52g97pGNCqxahGatyZKuC2O09r9MM= From ac08d681e4a1fb4fe2cc08a510fbe7b28b46134d Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Mon, 11 May 2026 13:08:28 +0900 Subject: [PATCH 110/117] fix(commerce): user-group URL parity + drop 3 dead endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - userGroup.userCreate/userDelete: /add_user, /remove_user → /user, /user/{userId} (routes.rb actual paths; the old endpoints never existed server-side) - Remove coupon.preview / point.previewUsage / point.calculateLimit methods and their request/response types — endpoints don't exist server-side (test/commerce/{couponPreview,pointPreviewUsage,pointCalculateLimit}.js deleted) Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.example | 25 +++ src/lib/commerce/modules/coupon.ts | 9 +- src/lib/commerce/modules/point.ts | 23 +-- src/lib/commerce/modules/user-group.ts | 4 +- src/lib/commerce/types/coupon.ts | 12 -- src/lib/commerce/types/point.ts | 27 --- test/AUDIT.md | 248 +++++++++++++++++++++++++ test/commerce/couponPreview.js | 25 --- test/commerce/pointCalculateLimit.js | 22 --- test/commerce/pointPreviewUsage.js | 25 --- test/config.js | 36 +++- 11 files changed, 312 insertions(+), 144 deletions(-) create mode 100644 test/AUDIT.md delete mode 100644 test/commerce/couponPreview.js delete mode 100644 test/commerce/pointCalculateLimit.js delete mode 100644 test/commerce/pointPreviewUsage.js diff --git a/.env.example b/.env.example index b648d16..994c62e 100644 --- a/.env.example +++ b/.env.example @@ -21,3 +21,28 @@ BOOTPAY_COMMERCE_CLIENT_KEY_PROD=JfF1ML0fWiXwnfpKRGvGOA BOOTPAY_COMMERCE_SECRET_KEY_PROD=MrNdlu26zkKc1axKJM2rj3DyOwOGJJpMKB9RxIKf0Pg= BOOTPAY_COMMERCE_CLIENT_KEY_DEV=ZYEi9d93uIaQFEuxXEZfyQ BOOTPAY_COMMERCE_SECRET_KEY_DEV=j8ONDlZQVHgAWq52g97pGNCqxahGatyZKuC2O09r9MM= + +# Commerce test fixtures — 실제 데이터 ID. 빈 값이면 해당 endpoint 테스트는 placeholder 로 동작. +# 검증 가능한 데이터로 채워야 detail/update/delete 류 테스트가 실제 통신. +BOOTPAY_TEST_COMMERCE_USER_ID= +BOOTPAY_TEST_COMMERCE_USER_GROUP_ID= +BOOTPAY_TEST_COMMERCE_PRODUCT_ID= +BOOTPAY_TEST_COMMERCE_CATEGORY_ID= +BOOTPAY_TEST_COMMERCE_COUPON_TEMPLATE_ID= +BOOTPAY_TEST_COMMERCE_INVOICE_ID= +BOOTPAY_TEST_COMMERCE_ORDER_ID= +BOOTPAY_TEST_COMMERCE_ORDER_NUMBER= +BOOTPAY_TEST_COMMERCE_ORDER_SUBSCRIPTION_ID= +BOOTPAY_TEST_COMMERCE_ORDER_SUBSCRIPTION_BILL_ID= +BOOTPAY_TEST_COMMERCE_ORDER_SUBSCRIPTION_ADJUSTMENT_ID= +BOOTPAY_TEST_COMMERCE_ORDER_CANCEL_REQUEST_HISTORY_ID= +BOOTPAY_TEST_COMMERCE_STAND_ID= + +# Commerce 공통 검색/필터 기본값 +BOOTPAY_TEST_COMMERCE_KEYWORD=테스트 +BOOTPAY_TEST_COMMERCE_S_AT=2024-01-01 +BOOTPAY_TEST_COMMERCE_E_AT=2099-12-31 + +# Commerce role default (user/manager/supervisor/vendor/partner) +# orderCancel.*, orderSubscriptionAdjustment.*, orderSubscription.update 류는 manager+ 필요. +BOOTPAY_TEST_COMMERCE_ROLE=user diff --git a/src/lib/commerce/modules/coupon.ts b/src/lib/commerce/modules/coupon.ts index 2196492..3695364 100644 --- a/src/lib/commerce/modules/coupon.ts +++ b/src/lib/commerce/modules/coupon.ts @@ -1,5 +1,5 @@ import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' -import { CommerceCoupon, CouponListParams, CouponPreviewParams, CouponDownloadParams } from '../types' +import { CommerceCoupon, CouponListParams, CouponDownloadParams } from '../types' export class CouponModule { private bootpay: BootpayCommerceResource @@ -29,13 +29,6 @@ export class CouponModule { return this.bootpay.get('coupon/available') } - /** - * 쿠폰 적용 미리보기 - */ - async preview(params: CouponPreviewParams): Promise> { - return this.bootpay.post('coupon/preview', params) - } - /** * 쿠폰 다운로드 (issue_from_template) */ diff --git a/src/lib/commerce/modules/point.ts b/src/lib/commerce/modules/point.ts index 9ab401d..7903c3f 100644 --- a/src/lib/commerce/modules/point.ts +++ b/src/lib/commerce/modules/point.ts @@ -2,11 +2,7 @@ import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce import { PointBalance, PointTransactionsParams, - PointTransactionsResponse, - PointPreviewUsageParams, - PointPreviewUsageResponse, - PointCalculateLimitParams, - PointCalculateLimitResponse + PointTransactionsResponse } from '../types' export class PointModule { @@ -43,21 +39,4 @@ export class PointModule { ) } - /** - * 적립금 사용 미리보기 - */ - async previewUsage( - params: PointPreviewUsageParams - ): Promise> { - return this.bootpay.post('point/preview_usage', params) - } - - /** - * 적립금 사용 한도 계산 - */ - async calculateLimit( - params: PointCalculateLimitParams - ): Promise> { - return this.bootpay.post('point/calculate_limit', params) - } } diff --git a/src/lib/commerce/modules/user-group.ts b/src/lib/commerce/modules/user-group.ts index 2d05710..79e7e27 100644 --- a/src/lib/commerce/modules/user-group.ts +++ b/src/lib/commerce/modules/user-group.ts @@ -57,7 +57,7 @@ export class UserGroupModule { * @param userId 사용자 ID */ async userCreate(userGroupId: string, userId: string): Promise> { - return this.bootpay.post(`user-groups/${userGroupId}/add_user`, { user_id: userId }) + return this.bootpay.post(`user-groups/${userGroupId}/user`, { user_id: userId }) } /** @@ -66,7 +66,7 @@ export class UserGroupModule { * @param userId 사용자 ID */ async userDelete(userGroupId: string, userId: string): Promise> { - return this.bootpay.delete(`user-groups/${userGroupId}/remove_user?user_id=${userId}`) + return this.bootpay.delete(`user-groups/${userGroupId}/user/${userId}`) } /** diff --git a/src/lib/commerce/types/coupon.ts b/src/lib/commerce/types/coupon.ts index 0268a93..18b05b0 100644 --- a/src/lib/commerce/types/coupon.ts +++ b/src/lib/commerce/types/coupon.ts @@ -21,18 +21,6 @@ export interface CouponListParams { limit?: number } -export interface CouponPreviewOrderItem { - order_product_id?: string - product_id?: string - qty?: number - price?: number -} - -export interface CouponPreviewParams { - coupon_ids: string[] - order_items: CouponPreviewOrderItem[] -} - export interface CouponDownloadParams { coupon_template_id: string } diff --git a/src/lib/commerce/types/point.ts b/src/lib/commerce/types/point.ts index b23469c..9a3bebc 100644 --- a/src/lib/commerce/types/point.ts +++ b/src/lib/commerce/types/point.ts @@ -34,30 +34,3 @@ export interface PointTransactionsParams { limit?: number transaction_type?: number } - -export interface PointPreviewUsageParams { - amount: number - order_total: number -} - -export interface PointPreviewUsageResponse { - current_balance?: number - use_amount?: number - balance_after?: number - order_total?: number - payment_amount?: number - is_valid?: boolean -} - -export interface PointCalculateLimitParams { - order_total: number -} - -export interface PointCalculateLimitResponse { - max_usable?: number - available_balance?: number - order_total?: number - max_rate?: number | null - min_usage?: number - reason?: string -} diff --git a/test/AUDIT.md b/test/AUDIT.md new file mode 100644 index 0000000..cea2c00 --- /dev/null +++ b/test/AUDIT.md @@ -0,0 +1,248 @@ +# NodeJS SDK 테스트 감사 보고서 + +기준: `server/nodejs` 2.5.0, `BOOTPAY_ENV=production`, `BOOTPAY_AUTH_MODE=new`. +실행: `test/pg/*.js` (26), `test/commerce/*.js` (64). + +--- + +## 요약 + +| 영역 | 정상 | 진짜 SDK/백엔드 버그 | 죽은 테스트 | 스테일 데이터 / placeholder | +|---|---:|---:|---:|---:| +| PG (26) | 4 | 2 | 3 | 17 | +| Commerce (64) | 7 | 9 list 타입 mismatch + 7 endpoint 404 + 3 500 | 0 | 7 role mismatch + 13종 fixture 누락 | + +--- + +## PG 결과 (26) + +### ✅ 정상 + +| 파일 | 비고 | +|---|---| +| `getAccessToken.js` | ck/sk + legacy 둘 다 동작 | +| `legacyCompatibility.js` | 두 모드 호환성 검증 통과 | +| `lookupBilling.js` | 정상 응답 (billing_key, billing_data 등) | +| `receiptPayment.js` | 정상 응답 (receipt 조회) | + +### 🐛 진짜 버그 — 백엔드 또는 SDK 수정 필요 + +| 파일 | ck/sk 결과 | legacy 결과 | 분석 | +|---|---|---|---| +| `requestUserToken.js` | `TOKEN_KEY_INVALID` | ✅ `user_token` 반환 | endpoint `POST request/user/token` 가 Basic Auth (ck/sk) 거부, Bearer 만 허용 | +| `getUserWallets.js` | `TOKEN_KEY_INVALID` | ✅ `[]` 반환 | 동일 — Bearer 만 허용 | + +→ **해결안 (택1)**: ① 백엔드가 ck/sk Basic Auth 받도록 수정 ② SDK 가 이 두 endpoint 만 자동으로 Bearer fallback ③ 이 두 endpoint 는 ck/sk 지원 불가로 문서화 후 legacy 강제. + +### 🧹 죽은/깨진 테스트 파일 — 삭제 또는 수정 + +| 파일 | 증상 | +|---|---| +| `form_payment_progress.js` | `ReferenceError: params is not defined` (line 19) | +| `request_payment.js` | 빈 출력 — entry point 없음 | +| `axios_test.js` | 하드코딩된 application_id (`-2410 not found`), config.js 우회 | + +### 🗓 스테일 데이터 (SDK 동작 정상, 픽스처 갱신 필요) + +| 파일 | 에러 코드 | +|---|---| +| `cancelPayment.js` | `RC_ALREADY_CANCELLED` | +| `confirmPayment.js` | `RC_NOT_CONFIRM_READY` | +| `cancelSubscribeReserve.js` | `SUBSCRIBE_BK_EXPIRED` | +| `destroySubscribeBillingKey.js` | `SUBSCRIBE_BK_EXPIRED` | +| `lookupSubscribeBilling.js` | `RC_NOT_SUBSCRIBE` | +| `subscribeCardPayment.js` | `SUBSCRIBE_BK_EXPIRED` | +| `subscribePayment.js` | `SUBSCRIBE_BK_EXPIRED` | +| `subscribePaymentReserve.js` | `SUBSCRIBE_BK_EXPIRED` | +| `cashReceiptPublishOnReceipt.js` | `RC_NOT_FOUND` | +| `shippingStart.js` | `RC_NOT_ESCROW` | +| `certificate.js` | `AUTH_EXPIRED` | +| `getBillingKey.js` | `RC_INVALID_EXP_MONTH` (테스트 카드 만료월 잘못됨) | +| `requestCashReceipt.js` | `RC_CASH_RECEIPT_FAILED` (PG MID 누락) | +| `publishAutomaticTransferBillingKey.js` | `SUBSCRIBE_PUBLISH_NOT_READY` | +| `requestSubscribeAutomaticTransferBillingKey.js` | `SUBSCRIBE_AT_LOOKUP_USER_FAILED` | +| `authenticateRequestRest.js` | `AUTH_CONFIRM_READY_FAILED` (통신사 정보 불일치) | +| `authenticateRealarmRest.js` | `AUTH_NOT_READY` | + +--- + +## Commerce 결과 (64) + +### ✅ 정상 200 OK (7) + +| 파일 | 비고 | +|---|---| +| `getAccessToken.js` | ck/sk 정상 발급 | +| `userList.js` | `{ list: [], count: 0 }` ← **타입 불일치** (SDK 선언은 `{ items, total }`) | +| `userGroupList.js` | 동일 | +| `productList.js` | 동일 (2건 데이터 있음) | +| `invoiceList.js` | `{ list, count, user }` ← **타입 불일치 + 추가 필드** | +| `orderList.js` | 동일 mismatch | +| `orderSubscriptionBillList.js` | 동일 mismatch | + +### 🐛 SDK 타입 계약 위반 — 모든 list endpoint + +| 모듈 | SDK 선언 (`modules/*.ts`) | 실제 서버 응답 | +|---|---|---| +| `user.list` | `Promise<{ items: CommerceUser[]; total: number }>` | `{ list, count }` | +| `userGroup.list` | `Promise<{ items: CommerceUserGroup[]; total: number }>` | `{ list, count }` | +| `product.list` | `Promise<{ items: CommerceProduct[]; total: number }>` | `{ list, count }` | +| `invoice.list` | `Promise<{ items: CommerceInvoice[]; total: number }>` | `{ list, count, user }` | +| `order.list` | `Promise<{ items: CommerceOrder[]; total: number }>` | `{ list, count }` | +| `orderCancel.list` | `Promise<{ items: ...; total }>` | (확인 필요) | +| `orderSubscription.list` | `Promise<{ items: ...; total }>` | `{ count, ing_pause, ing_purchase, ing_resume, ing_termination, ing_transfer, sum_active, sum_auto_end, sum_cancel_request, sum_first_payment_error, sum_hold_on, sum_pause, sum_pending, sum_terminated, sum_trial, total }` ← 통계 포함 풍부한 응답 | +| `orderSubscriptionBill.list` | `Promise<{ items: ...; total }>` | `{ list, count }` | +| `orderSubscriptionRequest.list` | `Promise<{ items: ...; total }>` | 404 | + +→ TypeScript 사용자가 `response.data.items` 로 접근 시 `undefined`. 기준 SDK 결정 후 6 SDK 전파 필요. + +### 🚫 404 Not Found — 전 role (user/manager/supervisor/vendor/partner) 동일 + +| 모듈 | URL | 비고 | +|---|---|---| +| `category.*` | `categories` (GET/POST/PUT/DELETE) | 모든 CRUD | +| `coupon.list` | `coupon` | | +| `coupon.available` | `coupon/available` | | +| `coupon.preview` | `coupon/preview` | | +| `coupon.download` | `coupon/download` | | +| `point.balance` | `point/balance` | | +| `point.transactions` | `point/transactions` | | +| `point.previewUsage` | `point/preview_usage` | | +| `point.calculateLimit` | `point/calculate_limit` | | +| `cart.orderPreview` | `cart/order-preview` | | +| `userGroup.userCreate` | `user-groups/{id}/add_user` | | +| `userGroup.userDelete` | `user-groups/{id}/remove_user` | | +| `orderSubscriptionRequest.list` | `order_subscriptions/requests` | | + +→ Production 미배포 / URL prefix 오류 / 다른 base 가능성. **백엔드 확인 필요.** + +### 🔐 API_ROLE_NOT_SUPPORT — manager+ role 필요 + +| 파일 | 모듈 | 필요 role 추정 | +|---|---|---| +| `orderCancelApprove.js` | `orderCancel.approve` | manager / supervisor | +| `orderCancelReject.js` | `orderCancel.reject` | manager / supervisor | +| `orderCancelRequest.js` | `orderCancel.request` | manager | +| `orderSubscriptionAdjustmentCreate.js` | `orderSubscriptionAdjustment.create` | manager | +| `orderSubscriptionAdjustmentUpdate.js` | `orderSubscriptionAdjustment.update` | manager | +| `orderSubscriptionAdjustmentDelete.js` | `orderSubscriptionAdjustment.delete` | manager | +| `orderSubscriptionUpdate.js` | `orderSubscription.update` | manager | + +→ 테스트가 `commerce.asManager()` 또는 `.withRole('manager')` 호출해야 함. + +### 🧨 SERVER_ERROR 500 + +| 파일 | 추정 원인 | +|---|---| +| `invoiceNotify.js` | `INVOICE_ID_HERE` placeholder 그대로 전송 | +| `productCreate.js` | multipart/form-data 처리 이슈 가능 | +| `orderSubscriptionList.js` | filter 에 placeholder 문자열 (`user_id: 'USER_ID_HERE'` 등) | + +### 📦 픽스처 누락 (placeholder 13종) + +| Placeholder | 사용 파일 수 | +|---|---| +| `USER_ID_HERE` | 4+ | +| `USER_GROUP_ID_HERE` | 4+ | +| `PRODUCT_ID_HERE` | 5+ | +| `CATEGORY_ID_HERE` | 4 | +| `COUPON_TEMPLATE_ID_HERE` | 1 | +| `INVOICE_ID_HERE` | 2 | +| `ORDER_ID_HERE` | 3 | +| `ORDER_NUMBER_HERE` | 2 | +| `ORDER_SUBSCRIPTION_ID_HERE` | 7+ | +| `ORDER_SUBSCRIPTION_BILL_ID_HERE` | 2 | +| `ORDER_SUBSCRIPTION_ADJUSTMENT_ID_HERE` | 2 | +| `ORDER_CANCEL_REQUEST_HISTORY_ID_HERE` | 3 | +| `STAND_ID_HERE` | 1 | + +→ `test/config.js` 의 `COMMERCE_TEST_DATA` 에 env-driven 으로 추가됨 (이번 turn). `.env` 의 `BOOTPAY_TEST_COMMERCE_*` 값을 실제 데이터로 채우고 각 테스트 파일을 `COMMERCE_TEST_DATA.user_id` 식으로 치환해야 실제 통신. + +--- + +## 우선순위 권장 + +| # | 작업 | 영향 범위 | 비고 | +|---:|---|---|---| +| 1 | list 응답 타입 mismatch 정리 | 9 modules × 7 SDK | 기준 결정 후 다른 6 server SDK 전파. (A) SDK 가 normalize ↔ (B) 타입을 `{ list, count }` 로 수정. | +| 2 | `requestUserToken` / `getUserWallets` ck/sk 거부 | 백엔드 또는 SDK | 백엔드 컨펌 필요 — 두 endpoint 의 Basic Auth 지원 여부. | +| 3 | 404 endpoint 7개 그룹 | 백엔드 | category/coupon/point/cart 등 prod 배포 여부 확인. | +| 4 | role 자동 적용 (orderCancel·orderSubscriptionAdjustment) | 테스트 패턴 | `.asManager()` 호출 또는 SDK 가 endpoint 별 자동 role 선택. | +| 5 | Commerce 픽스처 ID 실데이터로 채우기 | `.env` only | 이번 turn `COMMERCE_TEST_DATA` 인프라만 준비됨. 실제 prod 환경 데이터 ID 주입은 별도. | +| 6 | 죽은 PG 테스트 파일 3개 정리 | nodejs only | 삭제 또는 수정. | +| 7 | PG 스테일 픽스처 갱신 | nodejs only | 17개 테스트 — billing_key/reserve_id/receipt_id 새 데이터로 교체. | + +--- + +## SDK ↔ commerce-api `config/routes.rb` 대조 (2026-05-11) + +대조 대상: `multi-manager/projects/commerce-api/config/routes.rb` 의 `namespace :v1` (base `/v1/`). +SDK 측: `server/nodejs/src/lib/commerce/modules/*.ts` 의 모든 호출 URL. + +### A. 실제 SDK URL 버그 (5건) — SDK 수정 필요 + +| # | SDK 모듈 / 메서드 | SDK URL | 실제 라우트 (routes.rb) | 비고 | +|---:|---|---|---|---| +| 1 | `userGroup.userCreate` | `POST user-groups/:id/add_user` | `POST user-groups/:user_group_id/user` (line 89-92) | `namespace :user_groups do; scope '/:user_group_id' do; resources :user end end` → 컨트롤러는 `users#create`. body 의 `user_id` 그대로 전달 가능. | +| 2 | `userGroup.userDelete` | `DELETE user-groups/:id/remove_user?user_id=X` | `DELETE user-groups/:user_group_id/user/:id` (line 92) | RESTful — query param 대신 path 의 `:id` 로 전달. | +| 3 | `coupon.preview` | `POST coupon/preview` | (없음) — line 296 comment "@updated: 26-04-30 - preview 폐기" | `resources :coupon` (singular, user) 에서 제거됨. plural `coupons/preview` (line 143) 는 다른 scope. | +| 4 | `point.previewUsage` | `POST point/preview_usage` | (없음) — line 289 comment 로 제거됨 | `namespace :point` 는 balance / transactions 만 남음. | +| 5 | `point.calculateLimit` | `POST point/calculate_limit` | (없음) — 동상 | 동상. | + +### B. 프로덕션 배포 갭 (SDK 정상, 백엔드 prod 미배포) + +routes.rb 에는 존재 (최근 추가/이관). prod 에서 404 → dev 에서 200/401 확인됨. + +| 모듈 | SDK URL | routes.rb 라인 | 추가/변경 시점 | +|---|---|---:|---| +| `category.*` (CRUD) | `categories`, `categories/:id` | 320 | 2026-04-29 | +| `coupon.list` | `coupon` | 296 | V1 Phase A (2026-04-28~) | +| `coupon.available` | `coupon/available` | 298 | 동상 | +| `coupon.download` | `coupon/download` | 299 | 동상 | +| `point.balance` | `point/balance` | 290 | 동상 | +| `point.transactions` | `point/transactions` | 291 | 동상 | +| `cart.orderPreview` | `cart/order-preview` | 276 | 동상 | +| `orderSubscriptionRequest.list/detail/update` | `order-subscription-requests`, `.../:id` | 317 | 동상 | + +→ **백엔드팀에 prod 배포 일정 확인 필요.** 배포 후 자동으로 통과. + +### C. 매칭 OK (참고) + +다음은 routes.rb 와 일치하여 SDK 변경 불필요: + +- `users/login`, `users/login/token`, `users/authenticate/:id`, `users` CRUD, `users/:id/token` +- `user-groups` CRUD, `user-groups/:id/limit`, `user-groups/:id/aggregate-transaction` +- `invoices` CRUD, `invoices/:id/notify` +- `products` CRUD, `products/:id/status` +- `orders` list/detail, `orders/month` +- `order/cancel` list/create + `:id/withdraw|approve|reject` +- `order_subscriptions` CRUD + `:id/approve|reject|terminate|pause|resume` +- `order_subscriptions/:id/adjustments` (POST/PUT/DELETE) +- `order_subscriptions/requests/ing/{pause,resume,termination,calculate_termination_fee}` +- `order_subscription_bills` list/detail/update +- `store`, `store/detail` + +### D. 의존 검증 필요 + +| 항목 | 현재 SDK 동작 | 확인 사항 | +|---|---|---| +| `user.checkExist` → `GET users/join/:key?pk=:value` | 정상 200 (테스트 시) | routes 의 `resources :join` 가 show route 만 제공 — `:key` 가 path param 으로 정상 해석되는지 컨트롤러 측 검증 권장. | +| `PG: requestUserToken` ck/sk 거부 | TOKEN_KEY_INVALID | PG 측 (`api.bootpay.co.kr/v2`) routes 는 별도. Commerce routes.rb 와 무관. PG 백엔드팀 별도 확인. | + +### 권장 조치 순서 + +1. **A 항목 (#1~5)** — SDK 코드 수정. 다른 6 server SDK 에도 동일 URL 수정 전파. +2. **B 항목** — 백엔드팀 prod 배포 확정 후 테스트 재실행. SDK 변경 불필요. +3. **D 항목** — 컨트롤러/PG routes 별도 확인. + +--- + +## 실행 환경 + +``` +node 25.6.1 +@bootpay/backend-js 2.5.0 +BOOTPAY_ENV=production +BOOTPAY_AUTH_MODE=new (ck/sk Basic Auth) +실행일: 2026-05-11 +``` diff --git a/test/commerce/couponPreview.js b/test/commerce/couponPreview.js deleted file mode 100644 index 401bb42..0000000 --- a/test/commerce/couponPreview.js +++ /dev/null @@ -1,25 +0,0 @@ -const { getCommerceKeys } = require('../config.js'); -const keys = getCommerceKeys(); -// Commerce API - 쿠폰 적용 미리보기 - -(async () => { - const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') - - const commerce = new BootpayCommerce({ - client_key: keys.client_key, - secret_key: keys.secret_key, - mode: keys.mode - }) - - try { - // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. - // await commerce.getAccessToken() - const response = await commerce.coupon.preview({ - coupon_ids: [], - order_items: [] - }) - console.log('Coupon Preview:', JSON.stringify(response, null, 2)) - } catch (e) { - console.error('Error:', e) - } -})() diff --git a/test/commerce/pointCalculateLimit.js b/test/commerce/pointCalculateLimit.js deleted file mode 100644 index 357c9b7..0000000 --- a/test/commerce/pointCalculateLimit.js +++ /dev/null @@ -1,22 +0,0 @@ -const { getCommerceKeys } = require('../config.js'); -const keys = getCommerceKeys(); -// Commerce API - 적립금 사용 한도 계산 - -(async () => { - const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') - - const commerce = new BootpayCommerce({ - client_key: keys.client_key, - secret_key: keys.secret_key, - mode: keys.mode - }) - - try { - // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. - // await commerce.getAccessToken() - const response = await commerce.point.calculateLimit({ order_total: 10000 }) - console.log('Point Calculate Limit:', JSON.stringify(response, null, 2)) - } catch (e) { - console.error('Error:', e) - } -})() diff --git a/test/commerce/pointPreviewUsage.js b/test/commerce/pointPreviewUsage.js deleted file mode 100644 index bbed90c..0000000 --- a/test/commerce/pointPreviewUsage.js +++ /dev/null @@ -1,25 +0,0 @@ -const { getCommerceKeys } = require('../config.js'); -const keys = getCommerceKeys(); -// Commerce API - 적립금 사용 미리보기 - -(async () => { - const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') - - const commerce = new BootpayCommerce({ - client_key: keys.client_key, - secret_key: keys.secret_key, - mode: keys.mode - }) - - try { - // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. - // await commerce.getAccessToken() - const response = await commerce.point.previewUsage({ - amount: 1000, - order_total: 10000 - }) - console.log('Point Preview Usage:', JSON.stringify(response, null, 2)) - } catch (e) { - console.error('Error:', e) - } -})() diff --git a/test/config.js b/test/config.js index bcd0201..4b3bed8 100644 --- a/test/config.js +++ b/test/config.js @@ -80,7 +80,7 @@ const COMMERCE_CREDENTIALS = { } }; -// 테스트 데이터 +// PG 테스트 데이터 const TEST_DATA = { receipt_id: '628b2206d01c7e00209b6087', receipt_id_confirm: '62876963d01c7e00209b6028', @@ -97,6 +97,37 @@ const TEST_DATA = { certificate_receipt_id: '69fd7187564d1f550535538c' }; +// Commerce 테스트 fixture — placeholder 였던 ID 들을 .env 로 주입. +// 빈 값이면 해당 endpoint 는 placeholder 문자열이 그대로 들어가서 ORDER_NOT_FOUND / USER_NOT_FOUND 등으로 실패하므로 +// 실제 통신 검증을 위해선 .env 의 BOOTPAY_TEST_COMMERCE_* 키들을 채워야 한다. +const COMMERCE_TEST_DATA = { + user_id: env('BOOTPAY_TEST_COMMERCE_USER_ID', 'USER_ID_HERE'), + user_group_id: env('BOOTPAY_TEST_COMMERCE_USER_GROUP_ID', 'USER_GROUP_ID_HERE'), + product_id: env('BOOTPAY_TEST_COMMERCE_PRODUCT_ID', 'PRODUCT_ID_HERE'), + category_id: env('BOOTPAY_TEST_COMMERCE_CATEGORY_ID', 'CATEGORY_ID_HERE'), + coupon_template_id: env('BOOTPAY_TEST_COMMERCE_COUPON_TEMPLATE_ID', 'COUPON_TEMPLATE_ID_HERE'), + invoice_id: env('BOOTPAY_TEST_COMMERCE_INVOICE_ID', 'INVOICE_ID_HERE'), + order_id: env('BOOTPAY_TEST_COMMERCE_ORDER_ID', 'ORDER_ID_HERE'), + order_number: env('BOOTPAY_TEST_COMMERCE_ORDER_NUMBER', 'ORDER_NUMBER_HERE'), + order_subscription_id: env('BOOTPAY_TEST_COMMERCE_ORDER_SUBSCRIPTION_ID', 'ORDER_SUBSCRIPTION_ID_HERE'), + order_subscription_bill_id: env('BOOTPAY_TEST_COMMERCE_ORDER_SUBSCRIPTION_BILL_ID', 'ORDER_SUBSCRIPTION_BILL_ID_HERE'), + order_subscription_adjustment_id: env('BOOTPAY_TEST_COMMERCE_ORDER_SUBSCRIPTION_ADJUSTMENT_ID', 'ORDER_SUBSCRIPTION_ADJUSTMENT_ID_HERE'), + order_cancel_request_history_id: env('BOOTPAY_TEST_COMMERCE_ORDER_CANCEL_REQUEST_HISTORY_ID', 'ORDER_CANCEL_REQUEST_HISTORY_ID_HERE'), + stand_id: env('BOOTPAY_TEST_COMMERCE_STAND_ID', 'STAND_ID_HERE'), + keyword: env('BOOTPAY_TEST_COMMERCE_KEYWORD', '테스트'), + s_at: env('BOOTPAY_TEST_COMMERCE_S_AT', '2024-01-01'), + e_at: env('BOOTPAY_TEST_COMMERCE_E_AT', '2099-12-31') +}; + +// Commerce default role — orderCancel.*, orderSubscriptionAdjustment.*, orderSubscription.update 류는 manager+ 필요. +// 테스트는 commerce.withRole(COMMERCE_ROLE) 또는 endpoint 별 .asManager() 직접 호출. +const COMMERCE_ROLE = (env('BOOTPAY_TEST_COMMERCE_ROLE', 'user') || 'user').toLowerCase(); + +// fixture 가 placeholder 그대로면 true — 테스트는 이걸 보고 skip 결정 가능. +function isCommercePlaceholder(value) { + return typeof value === 'string' && value.endsWith('_HERE'); +} + function normalizeEnv(targetEnv) { return targetEnv || CURRENT_ENV; } @@ -135,6 +166,9 @@ module.exports = { PG_LEGACY_CREDENTIALS, COMMERCE_CREDENTIALS, TEST_DATA, + COMMERCE_TEST_DATA, + COMMERCE_ROLE, + isCommercePlaceholder, getPgKeys, getPgLegacyKeys, getActivePgConfig, From 5efc1fe45c305819f2e860595abc64cc45ad09dc Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Mon, 11 May 2026 13:52:27 +0900 Subject: [PATCH 111/117] docs(test): refresh AUDIT.md with 2026-05-11 re-run results - Mark A.1~A.5 SDK URL bugs as resolved (commit hashes for nodejs + 6 SDK propagation) - Record dev-vs-prod gap for 5 new modules (category/coupon/point/cart/order-subscription-request) added across 6 SDKs: routes exist, prod 404, dev returns API_ROLE_NOT_SUPPORT except category.list (200 []) - Add new anomaly: user.token returns INVOICE_TARGET_NOT_FOUND (server mapping bug suspected) - Expand SERVER_ERROR/validation table with userToken/userJoin/userLogin/etc. - Update priority list to reflect resolved vs outstanding work Co-Authored-By: Claude Opus 4.7 (1M context) --- test/AUDIT.md | 158 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 100 insertions(+), 58 deletions(-) diff --git a/test/AUDIT.md b/test/AUDIT.md index cea2c00..724b8fb 100644 --- a/test/AUDIT.md +++ b/test/AUDIT.md @@ -1,7 +1,19 @@ # NodeJS SDK 테스트 감사 보고서 기준: `server/nodejs` 2.5.0, `BOOTPAY_ENV=production`, `BOOTPAY_AUTH_MODE=new`. -실행: `test/pg/*.js` (26), `test/commerce/*.js` (64). +실행: `test/pg/*.js` (26), `test/commerce/*.js` (61). 최종 갱신: 2026-05-11. + +--- + +## 2026-05-11 진행 상황 (이후 섹션 읽기 전 필독) + +- **A 항목 #1~#5 (SDK URL 버그)**: 모두 해결됨. + - #1, #2 user-group URL 정정 → nodejs 커밋 `ac08d68`, 6 SDK 일괄 전파 (Go `df76c12` / Java `41d6f41` / Python `c225210` / PHP `e45386f` / .NET `1d0cf50` / Ruby `bba7237`). + - #3, #4, #5 죽은 endpoint (`coupon.preview`, `point.previewUsage`, `point.calculateLimit`) → 메서드 + 타입 + 테스트 파일 완전 삭제 (nodejs `ac08d68`). + - root 도 submodule pointer + 문서 갱신 (`69dba4d`). +- **B 항목 5 modules 일괄 추가**: category / coupon / point / cart / order-subscription-request → 6 server SDK 에 nodejs 기준으로 propagation 완료 (위 6 SDK 커밋과 동일). +- **새 발견**: dev 환경에서 5 modules endpoint 자체는 존재 확인. 그러나 production 미배포 (404). dev 에서도 `category.list` 만 200, 나머지는 `API_ROLE_NOT_SUPPORT` — user-token 인증 필요한 user-scoped endpoint 로 추정. **백엔드팀 확인 필요.** +- **새 anomaly**: `user.token('USER_ID_HERE')` 응답 메시지가 `INVOICE_TARGET_NOT_FOUND` — placeholder 가 invoice 도메인 에러로 매핑됨. 서버 측 매핑 버그 의심. --- @@ -10,7 +22,7 @@ | 영역 | 정상 | 진짜 SDK/백엔드 버그 | 죽은 테스트 | 스테일 데이터 / placeholder | |---|---:|---:|---:|---:| | PG (26) | 4 | 2 | 3 | 17 | -| Commerce (64) | 7 | 9 list 타입 mismatch + 7 endpoint 404 + 3 500 | 0 | 7 role mismatch + 13종 fixture 누락 | +| Commerce (61) | 7 | 9 list 타입 mismatch + 14 endpoint 404 (5 modules prod 미배포) + 4 SERVER_ERROR / 매핑 의심 | 0 | 9 role mismatch + 13종 fixture 누락 | --- @@ -96,25 +108,26 @@ → TypeScript 사용자가 `response.data.items` 로 접근 시 `undefined`. 기준 SDK 결정 후 6 SDK 전파 필요. -### 🚫 404 Not Found — 전 role (user/manager/supervisor/vendor/partner) 동일 +### 🚫 404 Not Found — production 환경 (2026-05-11 재실행) -| 모듈 | URL | 비고 | -|---|---|---| -| `category.*` | `categories` (GET/POST/PUT/DELETE) | 모든 CRUD | -| `coupon.list` | `coupon` | | -| `coupon.available` | `coupon/available` | | -| `coupon.preview` | `coupon/preview` | | -| `coupon.download` | `coupon/download` | | -| `point.balance` | `point/balance` | | -| `point.transactions` | `point/transactions` | | -| `point.previewUsage` | `point/preview_usage` | | -| `point.calculateLimit` | `point/calculate_limit` | | -| `cart.orderPreview` | `cart/order-preview` | | -| `userGroup.userCreate` | `user-groups/{id}/add_user` | | -| `userGroup.userDelete` | `user-groups/{id}/remove_user` | | -| `orderSubscriptionRequest.list` | `order_subscriptions/requests` | | - -→ Production 미배포 / URL prefix 오류 / 다른 base 가능성. **백엔드 확인 필요.** +| 모듈 | URL | dev 환경 결과 | 비고 | +|---|---|---|---| +| `category.list` | `GET categories` | ✅ 200 (`[]`) | 코드/URL OK. prod 미배포만 | +| `category.create/detail/update/delete` | `POST/GET/PUT/DELETE categories[/:id]` | (미테스트) | 동상 | +| `coupon.list` | `GET coupon` | ❌ `API_ROLE_NOT_SUPPORT` | dev 도 user-token 필요 추정 | +| `coupon.available` | `GET coupon/available` | ❌ 동상 | 동상 | +| `coupon.download` | `POST coupon/download` | (미테스트) | 동상 | +| `point.balance` | `GET point/balance` | ❌ `API_ROLE_NOT_SUPPORT` | user/manager/admin/partner/vendor/supervisor 전부 거부 | +| `point.transactions` | `GET point/transactions` | ❌ 동상 | 동상 | +| `cart.orderPreview` | `POST cart/order-preview` | ❌ 동상 | 동상 | +| `orderSubscriptionRequest.list` | `GET order-subscription-requests` | ❌ 동상 | 동상 | +| ~~`userGroup.userCreate`~~ | ~~`user-groups/{id}/add_user`~~ | — | ✅ 해결: `user-groups/{id}/user` | +| ~~`userGroup.userDelete`~~ | ~~`user-groups/{id}/remove_user`~~ | — | ✅ 해결: `user-groups/{id}/user/{userId}` | +| ~~`coupon.preview`~~ | ~~`POST coupon/preview`~~ | — | ✅ 해결: 메서드+타입+테스트 삭제 | +| ~~`point.previewUsage`~~ | ~~`POST point/preview_usage`~~ | — | ✅ 해결: 동상 | +| ~~`point.calculateLimit`~~ | ~~`POST point/calculate_limit`~~ | — | ✅ 해결: 동상 | + +→ **5 modules (category / coupon / point / cart / order-subscription-request)** routes.rb 에는 존재 (2026-04-28~04-29 추가). prod 는 아직 배포 안 됨 → 404. dev 는 endpoint 존재하나 ck/sk + BOOTPAY-ROLE 만으로는 인가 불가 — **user_token 컨텍스트 필요한 user-scoped endpoint 로 추정** (백엔드팀 확인 필요). ### 🔐 API_ROLE_NOT_SUPPORT — manager+ role 필요 @@ -127,16 +140,25 @@ | `orderSubscriptionAdjustmentUpdate.js` | `orderSubscriptionAdjustment.update` | manager | | `orderSubscriptionAdjustmentDelete.js` | `orderSubscriptionAdjustment.delete` | manager | | `orderSubscriptionUpdate.js` | `orderSubscription.update` | manager | +| `userGroupUserCreate.js` | `userGroup.userCreate` | manager (URL 정정 후 새로 노출됨) | +| `userGroupUserDelete.js` | `userGroup.userDelete` | manager (동상) | -→ 테스트가 `commerce.asManager()` 또는 `.withRole('manager')` 호출해야 함. +→ 테스트가 `commerce.asManager()` 또는 `.withRole('manager')` 호출해야 함. `config.js` 의 `BOOTPAY_TEST_COMMERCE_ROLE=manager` 환경변수로 일괄 토글 가능 (인프라 이미 있음). -### 🧨 SERVER_ERROR 500 +### 🧨 SERVER_ERROR 500 / 매핑 의심 / 검증 에러 -| 파일 | 추정 원인 | -|---|---| -| `invoiceNotify.js` | `INVOICE_ID_HERE` placeholder 그대로 전송 | -| `productCreate.js` | multipart/form-data 처리 이슈 가능 | -| `orderSubscriptionList.js` | filter 에 placeholder 문자열 (`user_id: 'USER_ID_HERE'` 등) | +| 파일 | error_code | 추정 원인 | +|---|---|---| +| `invoiceNotify.js` | `SERVER_ERROR` | `INVOICE_ID_HERE` placeholder 그대로 전송 | +| `productCreate.js` | `SERVER_ERROR` | multipart/form-data 처리 이슈 가능 | +| `orderSubscriptionList.js` | `SERVER_ERROR` | filter 에 placeholder 문자열 (`user_id: 'USER_ID_HERE'` 등) | +| `orderMonth.js` | `MONTHLY_BILLING_USER_GROUP_ID_INVALID` | `USER_GROUP_ID_HERE` placeholder | +| `userToken.js` | `INVOICE_TARGET_NOT_FOUND` | **🚨 서버 매핑 의심**: token endpoint 인데 invoice 도메인 에러 메시지. `user_id=USER_ID_HERE` placeholder 입력 시 응답. | +| `userJoin.js` | `USER_ID_INVALID` | 가입 가능한 user_id payload 필요 (현재 테스트 페이로드의 ID 형식 미스) | +| `userLogin.js` | `USER_LOGIN_FAILED` | 실제 login_id / login_pw 필요 | +| `userCheckExist.js` | `API_PARAM_INVALID` | param 형식 (`key=login_id&pk=value`) 백엔드 spec 재확인 | +| `userAuthenticationData.js` | `USER_STAND_BY_NOT_FOUND` | `STAND_ID_HERE` placeholder | +| `userGroupCreate.js` | `USER_BUSINESS_NUMBER_BLANK` | corporate_type=1 일 때 business_number 필수 — 테스트 payload 보완 | ### 📦 픽스처 누락 (placeholder 13종) @@ -160,17 +182,20 @@ --- -## 우선순위 권장 +## 우선순위 권장 (2026-05-11 최신) -| # | 작업 | 영향 범위 | 비고 | -|---:|---|---|---| -| 1 | list 응답 타입 mismatch 정리 | 9 modules × 7 SDK | 기준 결정 후 다른 6 server SDK 전파. (A) SDK 가 normalize ↔ (B) 타입을 `{ list, count }` 로 수정. | -| 2 | `requestUserToken` / `getUserWallets` ck/sk 거부 | 백엔드 또는 SDK | 백엔드 컨펌 필요 — 두 endpoint 의 Basic Auth 지원 여부. | -| 3 | 404 endpoint 7개 그룹 | 백엔드 | category/coupon/point/cart 등 prod 배포 여부 확인. | -| 4 | role 자동 적용 (orderCancel·orderSubscriptionAdjustment) | 테스트 패턴 | `.asManager()` 호출 또는 SDK 가 endpoint 별 자동 role 선택. | -| 5 | Commerce 픽스처 ID 실데이터로 채우기 | `.env` only | 이번 turn `COMMERCE_TEST_DATA` 인프라만 준비됨. 실제 prod 환경 데이터 ID 주입은 별도. | -| 6 | 죽은 PG 테스트 파일 3개 정리 | nodejs only | 삭제 또는 수정. | -| 7 | PG 스테일 픽스처 갱신 | nodejs only | 17개 테스트 — billing_key/reserve_id/receipt_id 새 데이터로 교체. | +| # | 작업 | 상태 | 영향 범위 | 비고 | +|---:|---|---|---|---| +| ~~1a~~ | A 항목 SDK URL 버그 5건 | ✅ 완료 | nodejs + 6 SDK | 커밋 해시 본문 참조 | +| ~~1b~~ | 5 modules 6 server SDK 일괄 추가 | ✅ 완료 | 6 SDK | nodejs reference parity | +| 2 | list 응답 타입 mismatch 정리 | 미해결 | 9 modules × 7 SDK | 기준 결정 후 6 server SDK 전파. (A) SDK 가 normalize ↔ (B) 타입을 `{ list, count }` 로 수정. | +| 3 | `requestUserToken` / `getUserWallets` ck/sk 거부 | 미해결 | 백엔드 또는 SDK | 백엔드 컨펌 필요 — 두 endpoint 의 Basic Auth 지원 여부. | +| 4 | 5 modules prod 배포 + 인증 모델 확인 | **백엔드 확인 대기** | 백엔드 | (a) prod 배포 일정 (b) user-scoped endpoint 인증 — ck/sk 만으로 가능한지 user_token 필요한지. | +| 5 | `user.token` 응답 매핑 (`INVOICE_TARGET_NOT_FOUND`) | 미해결 | 백엔드 | placeholder user_id 입력 시 invoice 도메인 에러로 매핑됨 — 서버 측 매핑 버그 의심. | +| 6 | role 자동 적용 (orderCancel·orderSubscriptionAdjustment·userGroup.user{Create,Delete}) | 미해결 | 테스트 패턴 | `.asManager()` 호출 또는 `BOOTPAY_TEST_COMMERCE_ROLE=manager` 자동 적용 (인프라 이미 있음). | +| 7 | Commerce 픽스처 ID 실데이터로 채우기 | 미해결 | `.env` only | `COMMERCE_TEST_DATA` 인프라 준비됨. 실 prod 데이터 ID 주입 별도. | +| 8 | 죽은 PG 테스트 파일 3개 정리 | 미해결 | nodejs only | 삭제 또는 수정. | +| 9 | PG 스테일 픽스처 갱신 | 미해결 | nodejs only | 17개 테스트 — billing_key/reserve_id/receipt_id 새 데이터로 교체. | --- @@ -179,32 +204,39 @@ 대조 대상: `multi-manager/projects/commerce-api/config/routes.rb` 의 `namespace :v1` (base `/v1/`). SDK 측: `server/nodejs/src/lib/commerce/modules/*.ts` 의 모든 호출 URL. -### A. 실제 SDK URL 버그 (5건) — SDK 수정 필요 +### A. 실제 SDK URL 버그 (5건) — ✅ **2026-05-11 전체 해결됨** -| # | SDK 모듈 / 메서드 | SDK URL | 실제 라우트 (routes.rb) | 비고 | +| # | SDK 모듈 / 메서드 | 이전 URL | 적용된 변경 | 전파 | |---:|---|---|---|---| -| 1 | `userGroup.userCreate` | `POST user-groups/:id/add_user` | `POST user-groups/:user_group_id/user` (line 89-92) | `namespace :user_groups do; scope '/:user_group_id' do; resources :user end end` → 컨트롤러는 `users#create`. body 의 `user_id` 그대로 전달 가능. | -| 2 | `userGroup.userDelete` | `DELETE user-groups/:id/remove_user?user_id=X` | `DELETE user-groups/:user_group_id/user/:id` (line 92) | RESTful — query param 대신 path 의 `:id` 로 전달. | -| 3 | `coupon.preview` | `POST coupon/preview` | (없음) — line 296 comment "@updated: 26-04-30 - preview 폐기" | `resources :coupon` (singular, user) 에서 제거됨. plural `coupons/preview` (line 143) 는 다른 scope. | -| 4 | `point.previewUsage` | `POST point/preview_usage` | (없음) — line 289 comment 로 제거됨 | `namespace :point` 는 balance / transactions 만 남음. | -| 5 | `point.calculateLimit` | `POST point/calculate_limit` | (없음) — 동상 | 동상. | +| 1 | `userGroup.userCreate` | `POST user-groups/:id/add_user` | → `POST user-groups/:id/user` | nodejs `ac08d68` + 6 SDK | +| 2 | `userGroup.userDelete` | `DELETE user-groups/:id/remove_user?user_id=X` | → `DELETE user-groups/:id/user/:userId` | 동상 | +| 3 | `coupon.preview` | `POST coupon/preview` | 메서드 + 타입 + 테스트 완전 삭제 | nodejs only (다른 SDK 미구현) | +| 4 | `point.previewUsage` | `POST point/preview_usage` | 동상 | 동상 | +| 5 | `point.calculateLimit` | `POST point/calculate_limit` | 동상 | 동상 | + +전파 커밋: Go `df76c12` · Java `41d6f41` · Python `c225210` · PHP `e45386f` · .NET `1d0cf50` · Ruby `bba7237`. root `69dba4d`. ### B. 프로덕션 배포 갭 (SDK 정상, 백엔드 prod 미배포) -routes.rb 에는 존재 (최근 추가/이관). prod 에서 404 → dev 에서 200/401 확인됨. +routes.rb 에는 존재 (2026-04-28 ~ 04-29 추가). prod 에서 404. **2026-05-11 dev 검증**: endpoint 존재 확인됨 (`category.list` 200 OK, 나머지 `API_ROLE_NOT_SUPPORT` — user-token 인증 필요 추정). + +5 modules 모두 **6 server SDK 에 일괄 추가됨 (2026-05-11)** — Go/Java/Python/PHP/.NET/Ruby. nodejs 기준 module 구조 그대로. -| 모듈 | SDK URL | routes.rb 라인 | 추가/변경 시점 | -|---|---|---:|---| -| `category.*` (CRUD) | `categories`, `categories/:id` | 320 | 2026-04-29 | -| `coupon.list` | `coupon` | 296 | V1 Phase A (2026-04-28~) | -| `coupon.available` | `coupon/available` | 298 | 동상 | -| `coupon.download` | `coupon/download` | 299 | 동상 | -| `point.balance` | `point/balance` | 290 | 동상 | -| `point.transactions` | `point/transactions` | 291 | 동상 | -| `cart.orderPreview` | `cart/order-preview` | 276 | 동상 | -| `orderSubscriptionRequest.list/detail/update` | `order-subscription-requests`, `.../:id` | 317 | 동상 | +| 모듈 | SDK URL | routes.rb 라인 | dev 검증 (2026-05-11) | 추가/변경 시점 | +|---|---|---:|---|---| +| `category.list` | `GET categories` | 320 | ✅ 200 (`[]`) | 2026-04-29 | +| `category.create/detail/update/delete` | `categories[/:id]` | 320 | 미테스트 | 동상 | +| `coupon.list` | `GET coupon` | 296 | ❌ `API_ROLE_NOT_SUPPORT` | V1 Phase A (2026-04-28~) | +| `coupon.available` | `GET coupon/available` | 298 | ❌ 동상 | 동상 | +| `coupon.download` | `POST coupon/download` | 299 | 미테스트 | 동상 | +| `point.balance` | `GET point/balance` | 290 | ❌ 동상 (user/manager/admin/partner/vendor/supervisor 전부 거부) | 동상 | +| `point.transactions` | `GET point/transactions` | 291 | ❌ 동상 | 동상 | +| `cart.orderPreview` | `POST cart/order-preview` | 276 | ❌ 동상 | 동상 | +| `orderSubscriptionRequest.list/detail/update` | `order-subscription-requests[/:id]` | 317 | ❌ 동상 | 동상 | -→ **백엔드팀에 prod 배포 일정 확인 필요.** 배포 후 자동으로 통과. +→ **백엔드팀 확인 필요**: +1. prod 배포 일정 (코드는 6 SDK 전파 완료 — 배포만 되면 즉시 동작). +2. user-scoped endpoint 인증 모델 — `coupon/point/cart/order-subscription-requests` 가 ck/sk + BOOTPAY-ROLE 만으로 인가되는지, 아니면 `user_token` 컨텍스트가 추가로 필요한지. ### C. 매칭 OK (참고) @@ -242,7 +274,17 @@ routes.rb 에는 존재 (최근 추가/이관). prod 에서 404 → dev 에서 2 ``` node 25.6.1 @bootpay/backend-js 2.5.0 -BOOTPAY_ENV=production +BOOTPAY_ENV=production (재실행 시 BOOTPAY_ENV=development 로 dev 검증 일부 포함) BOOTPAY_AUTH_MODE=new (ck/sk Basic Auth) -실행일: 2026-05-11 +실행일: 2026-05-11 (최종 갱신) ``` + +## 2026-05-11 commerce/*.js 재실행 결과 요약 + +총 61 파일. 모두 syntax/load OK. 실제 서버 응답 기준: + +- ✅ 200 success (7): `getAccessToken`, `invoiceList`, `orderList`, `orderSubscriptionBillList`, `productList`, `userGroupList`, `userList` +- 🚫 404 (prod 미배포, 14): `cartOrderPreview` · category 5건 · coupon 3건 · point 2건 · `orderSubscriptionRequestList` +- 🔐 `API_ROLE_NOT_SUPPORT` (9): orderCancel 3건 + orderSubscriptionAdjustment 3건 + `orderSubscriptionUpdate` + userGroup.user 2건 +- 📦 placeholder fixture (21): USER_NOT_FOUND / ORDER_NOT_FOUND / PRODUCT_NOT_FOUND / USER_GROUP_NOT_FOUND / ORDER_SUBSCRIPTION_*_NOT_FOUND / INVOICE_*_NOT_FOUND +- 🧨 기타 (10): SERVER_ERROR 3건 + 매핑 의심·검증 에러 7건 — 본문 D 표 참조 From 78212860b8bc6552eaec71ea71e09918b6974047 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Thu, 14 May 2026 14:33:12 +0900 Subject: [PATCH 112/117] chore(release): 2.6.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CHANGELOG.md 파일명 오타 정정 (CHNAGELOG.md → CHANGELOG.md) * 2.5.0 미공개 → 2.6.0 으로 격상 (userGroup URL parity + dead endpoint 3종 제거 포함) --- CHNAGELOG.md => CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) rename CHNAGELOG.md => CHANGELOG.md (85%) diff --git a/CHNAGELOG.md b/CHANGELOG.md similarity index 85% rename from CHNAGELOG.md rename to CHANGELOG.md index 49d3277..b2119ca 100644 --- a/CHNAGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,16 @@ -### 2.5.0 +### 2.6.0 * 인증: client_key/secret_key Basic Auth 지원 (PG + Commerce 공통) - 기존 application_id/private_key Bearer 방식 하위 호환 유지 - ck/sk 모드에서는 request/token 호출 불필요 (getAccessToken 합성 응답) - ck 또는 sk 한쪽만 지정 + legacy 키도 없으면 NEED_CLIENT_KEY(-101) reject * Commerce: V1 신설 모듈 추가 — category, coupon, point, orderSubscriptionRequest, cart - cart.orderPreview: 권위적 배송비/할인 계산 응답 (guest/member 모드) +* Commerce: userGroup URL parity 정정 — `/add_user` → `/user`, `/remove_user` → `/user/{userId}` (서버 routes.rb 와 정렬, 옛 URL 은 서버 미존재) +* Commerce: 서버에 존재하지 않는 endpoint 3종 제거 (`coupon.preview`, `point.previewUsage`, `point.calculateLimit`) — npm 미공개 모듈이라 사용자 영향 없음 * Wallet API (`requestWalletPayment`, `WalletRequestParameters`, `WalletPaymentResponseParameters`) `@deprecated` 표시 — 다음 메이저 버전에서 제거 예정 * `http_status` 응답 필드 `@deprecated` 표시 — 다음 메이저 버전에서 제거 예정 (성공 여부는 `status` 필드 사용) * 테스트 인프라: `.env` / `BOOTPAY_AUTH_MODE=new|legacy` 토글로 ck/sk · legacy 양쪽 검증, PG 테스트 디렉터리 분리(`test/pg/`) +* docs: CHANGELOG 파일명 오타 정정 (`CHNAGELOG.md` → `CHANGELOG.md`) ### 2.4.1 * Commerce 응답포맷 개선 diff --git a/package.json b/package.json index 4589532..f06198c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.5.0", + "version": "2.6.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", From 7b8339be70868d5537f5652678f42730b47dc818 Mon Sep 17 00:00:00 2001 From: rupy1014 Date: Thu, 14 May 2026 15:27:31 +0900 Subject: [PATCH 113/117] test: load API keys exclusively from .env (no hardcoded fallback) - tests_basic_auth_product_info.mjs: read BOOTPAY_COMMERCE_{CLIENT_KEY,SECRET_KEY}_{PROD,DEV} via .env loader, exit 2 if missing - .npmignore: exclude tests_*.mjs / .env / .env.example from published tarball Co-Authored-By: Claude Opus 4.7 (1M context) --- .npmignore | 4 +++ tests_basic_auth_product_info.mjs | 53 ++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/.npmignore b/.npmignore index 630bbe3..1633382 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,9 @@ src/* test/* +tests_*.mjs +.env +.env.example +.env.* .gitattributes .gitignore package-lock.json diff --git a/tests_basic_auth_product_info.mjs b/tests_basic_auth_product_info.mjs index cc5fd28..40c624f 100644 --- a/tests_basic_auth_product_info.mjs +++ b/tests_basic_auth_product_info.mjs @@ -1,8 +1,53 @@ +/** + * Commerce Basic Auth smoke test — fetch 로 /products 한 번 호출하고 응답 확인. + * + * 키는 반드시 .env 또는 환경변수로 주입한다 (.env.example 참고). + * - BOOTPAY_ENV=production|development|stage + * - BOOTPAY_COMMERCE_CLIENT_KEY_{PROD|DEV} + * - BOOTPAY_COMMERCE_SECRET_KEY_{PROD|DEV} + */ import { Buffer } from 'node:buffer'; +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; -const clientKey = process.env.BP_CLIENT_KEY || 'QIzXk4M3EeD-6B1GTfmGHA'; -const secretKey = process.env.BP_SECRET_KEY || 'vRle44QfyBj7nzJlBbeebqkbtlJVRTS2DQa9Adpz3d8='; -const baseUrl = process.env.BP_BASE_URL || 'https://dev-api.bootapi.com/v1'; +const __dirname = dirname(fileURLToPath(import.meta.url)); + +function loadDotEnv() { + for (const file of [resolve(__dirname, '.env'), resolve(__dirname, 'test', '.env')]) { + if (!existsSync(file)) continue; + for (const raw of readFileSync(file, 'utf8').split(/\r?\n/)) { + const line = raw.trim(); + if (!line || line.startsWith('#') || !line.includes('=')) continue; + const idx = line.indexOf('='); + const key = line.slice(0, idx).trim(); + let value = line.slice(idx + 1).trim(); + if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + if (process.env[key] === undefined) process.env[key] = value; + } + } +} + +loadDotEnv(); + +const env = (process.env.BOOTPAY_ENV || 'production').toLowerCase(); +const baseUrlMap = { + production: 'https://api.bootapi.com/v1', + stage: 'https://stage-api.bootapi.com/v1', + development: 'https://dev-api.bootapi.com/v1' +}; +const baseUrl = baseUrlMap[env] || baseUrlMap.production; + +const suffix = env === 'production' ? 'PROD' : 'DEV'; +const clientKey = process.env[`BOOTPAY_COMMERCE_CLIENT_KEY_${suffix}`] || ''; +const secretKey = process.env[`BOOTPAY_COMMERCE_SECRET_KEY_${suffix}`] || ''; + +if (!clientKey || !secretKey) { + console.error(`[smoke] missing BOOTPAY_COMMERCE_CLIENT_KEY_${suffix} / BOOTPAY_COMMERCE_SECRET_KEY_${suffix} — set them in .env (see .env.example)`); + process.exit(2); +} const basic = Buffer.from(`${clientKey}:${secretKey}`).toString('base64'); const res = await fetch(`${baseUrl}/products?page=1&limit=1`, { @@ -17,5 +62,5 @@ const res = await fetch(`${baseUrl}/products?page=1&limit=1`, { }); const body = await res.text(); -console.log(JSON.stringify({ status: res.status, ok: res.ok, preview: body.slice(0, 500) }, null, 2)); +console.log(JSON.stringify({ env, status: res.status, ok: res.ok, preview: body.slice(0, 500) }, null, 2)); if (!res.ok) process.exit(1); From 5a4009fb15153eb86aa67e972361dfa29a3f627e Mon Sep 17 00:00:00 2001 From: Bootpay SDK Bot Date: Fri, 14 Aug 2026 06:48:18 +0000 Subject: [PATCH 114/117] =?UTF-8?q?sync:=20817dbe80=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source: git.bootpay.co.kr/bootpay-sdk/backend-ruby@817dbe804b30af68817b912c2fde28b787040ff2 --- src/lib/commerce-resource.ts | 23 +++++++-- src/lib/commerce/modules/product.ts | 2 +- test/commerce/authorizationHeader.js | 73 ++++++++++++++++++++++++++++ test/test.md | 4 ++ 4 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 test/commerce/authorizationHeader.js diff --git a/src/lib/commerce-resource.ts b/src/lib/commerce-resource.ts index 6d70ab0..784c047 100644 --- a/src/lib/commerce-resource.ts +++ b/src/lib/commerce-resource.ts @@ -76,9 +76,9 @@ export class BootpayCommerceResource { config.headers.set('BOOTPAY-SDK-TYPE', '301') config.headers.set('BOOTPAY-ROLE', this.$role || 'user') - const basicAuth = this.getBasicAuthHeader() - if (basicAuth) { - config.headers.set('Authorization', basicAuth) + const authorization = this.authorizationHeader() + if (authorization) { + config.headers.set('Authorization', authorization) } return config }, @@ -115,6 +115,23 @@ export class BootpayCommerceResource { return this.$role } + /** + * Authorization 헤더 + * 토큰이 발급되어 있으면 Bearer, 없으면 client_key/secret_key Basic Auth 를 사용한다. + */ + authorizationHeader(): string { + const token = this.$token + if (token !== undefined && token !== '') { + return `Bearer ${token}` + } + return this.getBasicAuthHeader() + } + + /** + * client_key/secret_key Basic Auth 헤더 + * 계산 결과를 $token 에 저장하지 않는다 — 저장하면 다음 요청부터 Basic 값이 + * Bearer 토큰으로 오인되어 인증이 깨진다. + */ private getBasicAuthHeader(): string { const { client_key, secret_key } = this.commerceConfiguration if (client_key && secret_key) { diff --git a/src/lib/commerce/modules/product.ts b/src/lib/commerce/modules/product.ts index 1cccd20..ca10fad 100644 --- a/src/lib/commerce/modules/product.ts +++ b/src/lib/commerce/modules/product.ts @@ -72,7 +72,7 @@ export class ProductModule { return this.bootpay.$http.post(url, formData, { headers: { ...formData.getHeaders(), - Authorization: `Bearer ${this.bootpay.getToken()}`, + Authorization: this.bootpay.authorizationHeader(), 'BOOTPAY-ROLE': this.bootpay.getRole() || 'user' } }) diff --git a/test/commerce/authorizationHeader.js b/test/commerce/authorizationHeader.js new file mode 100644 index 0000000..534ced7 --- /dev/null +++ b/test/commerce/authorizationHeader.js @@ -0,0 +1,73 @@ +const assert = require('assert'); +const { BootpayCommerce } = require('../../dist/bootpay-commerce.js'); + +function header(config, name) { + if (!config.headers) return undefined; + if (typeof config.headers.get === 'function') return config.headers.get(name); + return config.headers[name] || config.headers[name.toLowerCase()] || config.headers[name.toUpperCase()]; +} + +// Commerce API - Authorization 헤더 선택 규칙 테스트 (네트워크 호출 없음) +// 1) 토큰이 없으면 client_key/secret_key Basic Auth +// 2) 토큰이 있으면 Bearer 우선 +// 3) Basic Auth 값이 토큰을 오염시키지 않아야 한다 (연속 요청에도 Basic 유지) + +(async () => { + const commerce = new BootpayCommerce({ + client_key: 'ck', + secret_key: 'sk', + mode: 'development' + }); + + const requests = []; + commerce.$http.defaults.adapter = async (config) => { + requests.push(config); + return { + data: { access_token: 'commerce_access_token' }, + status: 200, + statusText: 'OK', + headers: {}, + config, + request: {} + }; + }; + + const basic = `Basic ${Buffer.from('ck:sk').toString('base64')}`; + + // 1) 토큰 미발급 상태 — Basic Auth 부착 + await commerce.product.list(); + assert.strictEqual(header(requests[0], 'Authorization'), basic); + + // 2) 연속 요청에도 Basic 이 그대로 유지 (Basic 값이 $token 으로 새어나가면 안 된다) + await commerce.product.list(); + assert.strictEqual(header(requests[1], 'Authorization'), basic); + assert.strictEqual(commerce.getToken(), undefined, 'basic auth must not populate the token'); + assert.strictEqual(commerce.hasToken(), false); + + // 3) request/token 요청 자체는 Basic Auth 로 나가고, 발급된 토큰이 저장된다 + const tokenResponse = await commerce.getAccessToken(); + assert.strictEqual(tokenResponse.access_token, 'commerce_access_token'); + assert.strictEqual(header(requests[2], 'Authorization'), basic); + assert.strictEqual(commerce.getCurrentToken(), 'commerce_access_token'); + + // 4) 토큰 발급 이후에는 Bearer 가 우선한다 + await commerce.product.list(); + assert.strictEqual(header(requests[3], 'Authorization'), 'Bearer commerce_access_token'); + + // 5) setToken 으로 직접 넣은 토큰도 동일하게 Bearer 우선 + commerce.setToken('manual_token'); + await commerce.product.list(); + assert.strictEqual(header(requests[4], 'Authorization'), 'Bearer manual_token'); + + // 6) 키/토큰이 모두 없으면 Authorization 헤더를 붙이지 않는다 + const anonymous = new BootpayCommerce({ mode: 'development' }); + anonymous.$http.defaults.adapter = commerce.$http.defaults.adapter; + const beforeAnonymous = requests.length; + await anonymous.product.list(); + assert.ok(!header(requests[beforeAnonymous], 'Authorization'), 'no key/token must not send an Authorization header'); + + console.log('commerce authorization header: bearer takes precedence, basic auth stays stable'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/test.md b/test/test.md index 7fc96cf..24a0e8d 100644 --- a/test/test.md +++ b/test/test.md @@ -66,6 +66,9 @@ node test/pg/cashReceiptPublishOnReceipt.js ```bash # Commerce 테스트 실행 node test/commerce/[테스트파일].js + +# Authorization 헤더 선택 규칙 검증 (네트워크 호출 없음, 키 불필요) +node test/commerce/authorizationHeader.js ``` ## 테스트 데이터 @@ -162,3 +165,4 @@ Bootpay.setConfiguration(getActivePgConfig()); - `test/pg/getAccessToken.js` - `test/legacyCompatibility.js` +- `test/commerce/authorizationHeader.js` (Commerce — 실제 통신 없이 mock adapter 로 헤더만 검증) From 6f0523374f6e5b86a3cbd7067d6fcfc3fbd2216b Mon Sep 17 00:00:00 2001 From: Bootpay SDK Bot Date: Fri, 14 Aug 2026 07:23:34 +0000 Subject: [PATCH 115/117] =?UTF-8?q?sync:=20817dbe80=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source: git.bootpay.co.kr/bootpay-sdk/backend-ruby@817dbe804b30af68817b912c2fde28b787040ff2 --- .env.example | 5 + CHANGELOG.md | 11 + README.md | 54 ++++ package.json | 2 +- src/bootpay-commerce.ts | 3 + src/bootpay.ts | 18 ++ src/lib/commerce-resource.ts | 5 +- src/lib/commerce/modules/index.ts | 1 + src/lib/commerce/modules/mall-setting.ts | 70 ++++++ .../commerce/modules/order-subscription.ts | 58 ++++- src/lib/commerce/types/index.ts | 1 + src/lib/commerce/types/mall-setting.ts | 236 ++++++++++++++++++ src/lib/commerce/types/order-subscription.ts | 42 ++++ test/commerce/mallSettingDetail.js | 25 ++ test/commerce/mallSettingRequest.js | 78 ++++++ test/commerce/mallSettingUpdate.js | 33 +++ test/commerce/orderSubscriptionCharge.js | 32 +++ .../orderSubscriptionChargeRequest.js | 107 ++++++++ .../commerce/orderSubscriptionChargeRevoke.js | 29 +++ test/config.js | 5 +- test/pg/lookupSequentialBillingKey.js | 16 ++ test/pg/lookupSequentialBillingKeyRequest.js | 34 +++ test/test.md | 18 ++ 23 files changed, 879 insertions(+), 4 deletions(-) create mode 100644 src/lib/commerce/modules/mall-setting.ts create mode 100644 src/lib/commerce/types/mall-setting.ts create mode 100644 test/commerce/mallSettingDetail.js create mode 100644 test/commerce/mallSettingRequest.js create mode 100644 test/commerce/mallSettingUpdate.js create mode 100644 test/commerce/orderSubscriptionCharge.js create mode 100644 test/commerce/orderSubscriptionChargeRequest.js create mode 100644 test/commerce/orderSubscriptionChargeRevoke.js create mode 100644 test/pg/lookupSequentialBillingKey.js create mode 100644 test/pg/lookupSequentialBillingKeyRequest.js diff --git a/.env.example b/.env.example index 994c62e..1393e44 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,9 @@ BOOTPAY_PG_SECRET_KEY_PROD=L15AxIjXxGwFj9xe7NUUOt9VPHqP-CvyXJuK-FqMHto= BOOTPAY_PG_CLIENT_KEY_DEV=K1Xok7RzFxbT7zMBmiBXNw BOOTPAY_PG_SECRET_KEY_DEV=vcd_5OXoQAxTA8JSg2VGaSnwmQPkd8DgQ6xiyL6QkyE= +# PG 테스트 fixture — 우선순위(순차) 결제 빌링키 조회용 위젯키 +BOOTPAY_TEST_PG_WIDGET_KEY= + # PG API - legacy application_id/private_key (호환성 검증용) BOOTPAY_PG_APPLICATION_ID_PROD=5b8f6a4d396fa665fdc2b5ea BOOTPAY_PG_PRIVATE_KEY_PROD=rm6EYECr6aroQVG2ntW0A6LpWnkTgP4uQ3H18sDDUYw= @@ -37,6 +40,8 @@ BOOTPAY_TEST_COMMERCE_ORDER_SUBSCRIPTION_BILL_ID= BOOTPAY_TEST_COMMERCE_ORDER_SUBSCRIPTION_ADJUSTMENT_ID= BOOTPAY_TEST_COMMERCE_ORDER_CANCEL_REQUEST_HISTORY_ID= BOOTPAY_TEST_COMMERCE_STAND_ID= +# 수시결제(온디맨드) charge_key — supervisor 전용 order_subscriptions/charge 테스트용 +BOOTPAY_TEST_COMMERCE_CHARGE_KEY= # Commerce 공통 검색/필터 기본값 BOOTPAY_TEST_COMMERCE_KEYWORD=테스트 diff --git a/CHANGELOG.md b/CHANGELOG.md index b2119ca..f4f8e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +### 2.7.0 +* PG: 우선순위(순차) 결제 빌링키 조회 `lookupSequentialBillingKey(widgetKey, billingKey)` 추가 — `GET subscribe/sequential_billing_key/{billing_key}?widget_key={widget_key}` +* Commerce: 수시결제(온디맨드) charge_key 결제/해지 추가 (supervisor 전용) + - `orderSubscription.supervisorCharge`: `POST order_subscriptions/charge` — charge_key 는 body 로만 전송 (URL/query 금지) + - `orderSubscription.supervisorChargeRevoke`: `DELETE order_subscriptions/charge` — 해지 후 해당 키로 재결제 불가 + - 두 endpoint 모두 `Idempotency-Key` 헤더 자동 생성 (`idempotency_key` 파라미터로 직접 지정 가능) +* Commerce: 몰 설정 모듈 `mallSetting` 추가 (supervisor 전용) + - `getMallSetting`/`detail`: `GET mall-setting` + - `updateMallSetting`/`update`: `PUT mall-setting` — flatten 바디, null/undefined 값은 전송하지 않음 +* Commerce: 요청별로 지정된 `BOOTPAY-ROLE` 헤더를 인터셉터가 덮어쓰지 않도록 수정 (supervisor 전용 endpoint 대응, 미지정시 기존 동작 그대로) + ### 2.6.0 * 인증: client_key/secret_key Basic Auth 지원 (PG + Commerce 공통) - 기존 application_id/private_key Bearer 방식 하위 호환 유지 diff --git a/README.md b/README.md index 1340ebd..1fa934c 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 - [4-6. 예약 취소하기](#4-6-예약-취소하기) - [4-7. 빌링키 삭제하기](#4-7-빌링키-삭제하기) - [4-8. 빌링키 조회하기](#4-8-빌링키-조회하기) + - [4-9. 우선순위 결제 빌링키 조회하기](#4-9-우선순위-결제-빌링키-조회하기) - [5. 회원 토큰 발급요청](#5-회원-토큰-발급요청) - [6. 서버 승인 요청](#6-서버-승인-요청) - [7. 본인 인증 결과 조회](#7-본인-인증-결과-조회) @@ -38,6 +39,7 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 - [10-4. 주문 관리](#10-4-주문-관리) - [10-5. 정기구독 관리](#10-5-정기구독-관리) - [10-6. 청구서 관리](#10-6-청구서-관리) + - [10-7. 몰 설정 관리](#10-7-몰-설정-관리) - [Example 프로젝트](#example-프로젝트) - [Documentation](#documentation) - [기술문의](#기술문의) @@ -395,6 +397,24 @@ const response = await Bootpay.lookupBillingKey('66542dfb4d18d5fc7b43e1b6') console.log(response) ``` +## 4-9. 우선순위 결제 빌링키 조회하기 +우선순위(순차) 결제에 사용되는 빌링키를 위젯키와 함께 조회합니다. +```javascript +(async () => { + Bootpay.setConfiguration({ + client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD, + secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD + }) + try { + await Bootpay.getAccessToken() + const response = await Bootpay.lookupSequentialBillingKey('WIDGET_KEY', '66542dfb4d18d5fc7b43e1b6') + console.log(response) + } catch (e) { + console.log(e) + } +})() +``` + ## 5. 회원 토큰 발급요청 ㅇㅇ페이 사용을 위해 가맹점 회원의 토큰을 발급합니다. 가맹점은 회원의 고유번호를 관리해야합니다. @@ -600,6 +620,21 @@ await commerce.orderSubscription.termination({ order_subscription_id: 'ORDER_SUBSCRIPTION_ID', reason: '해지 사유' }) + +// 수시결제(온디맨드) charge_key 즉시 결제 — supervisor 전용 +// charge_key 는 body 로만 전송됩니다 (URL/query 금지 — 액세스 로그 노출 방지) +await commerce.asSupervisor().orderSubscription.supervisorCharge({ + charge_key: 'CHARGE_KEY', + price: 1000, + tax_free_price: 0, + user: { id: 'USER_ID' }, + metadata: { memo: '수시결제' } +}) + +// 수시결제(온디맨드) charge_key 해지 — 해지 이후 해당 키로의 재결제는 불가능합니다 +await commerce.asSupervisor().orderSubscription.supervisorChargeRevoke({ + charge_key: 'CHARGE_KEY' +}) ``` ### 10-6. 청구서 관리 @@ -619,6 +654,25 @@ const invoice = await commerce.invoice.create({ await commerce.invoice.notify('INVOICE_ID', [1, 2]) // 1: SMS, 2: Email ``` +### 10-7. 몰 설정 관리 + +supervisor scope 토큰(또는 키)으로만 호출할 수 있습니다. + +```javascript +// 몰 설정 조회 +const mallSetting = await commerce.mallSetting.getMallSetting() + +// 몰 설정 수정 — 전달한 값(non-null)만 서버로 전송됩니다 +await commerce.mallSetting.updateMallSetting({ + name: '부트페이몰', + description: '몰 소개', + use_cart: true, + cart_max_limit: 100, + use_point: true, + point_rate: 1 +}) +``` + 더 자세한 Commerce API 사용 예제는 [test/commerce](./test/commerce) 디렉토리를 참고해주세요. ## Example 프로젝트 diff --git a/package.json b/package.json index f06198c..e614e26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.6.0", + "version": "2.7.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", diff --git a/src/bootpay-commerce.ts b/src/bootpay-commerce.ts index 48bd1b9..48c2478 100644 --- a/src/bootpay-commerce.ts +++ b/src/bootpay-commerce.ts @@ -14,6 +14,7 @@ import { CouponModule } from './lib/commerce/modules/coupon' import { PointModule } from './lib/commerce/modules/point' import { CartModule } from './lib/commerce/modules/cart' import { StoreModule } from './lib/commerce/modules/store' +import { MallSettingModule } from './lib/commerce/modules/mall-setting' export interface CommerceTokenResponse { access_token: string @@ -36,6 +37,7 @@ export class BootpayCommerce extends BootpayCommerceResource { public point!: PointModule public cart!: CartModule public store!: StoreModule + public mallSetting!: MallSettingModule constructor(configuration?: CommerceConfiguration) { super() @@ -61,6 +63,7 @@ export class BootpayCommerce extends BootpayCommerceResource { this.point = new PointModule(this) this.cart = new CartModule(this) this.store = new StoreModule(this) + this.mallSetting = new MallSettingModule(this) } /** diff --git a/src/bootpay.ts b/src/bootpay.ts index 2101e73..217d0f0 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -158,6 +158,24 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { } } + /** + * lookupSequentialBillingKey + * 우선순위(순차) 결제 빌링키 조회 + * Comment by GOSOMI + * @date: 2026-07-03 + * @param widgetKey: string + * @param billingKey: string + * @returns Promise + */ + async lookupSequentialBillingKey(widgetKey: string, billingKey: string): Promise { + try { + const response: SubscriptionBillingResponseParameters = await this.get(`subscribe/sequential_billing_key/${ billingKey }?widget_key=${ encodeURIComponent(widgetKey) }`) + return Promise.resolve(response) + } catch (e) { + return Promise.reject(e) + } + } + /** * requestSubscribeBillingKey * Comment by GOSOMI diff --git a/src/lib/commerce-resource.ts b/src/lib/commerce-resource.ts index 784c047..32ae69d 100644 --- a/src/lib/commerce-resource.ts +++ b/src/lib/commerce-resource.ts @@ -74,7 +74,10 @@ export class BootpayCommerceResource { config.headers.set('BOOTPAY-SDK-VERSION', this.sdkVersion) config.headers.set('BOOTPAY-API-VERSION', this.apiVersion) config.headers.set('BOOTPAY-SDK-TYPE', '301') - config.headers.set('BOOTPAY-ROLE', this.$role || 'user') + // 요청별로 role 이 지정된 경우(supervisor 전용 endpoint 등)에는 그 값을 유지한다. + if (!config.headers.has('BOOTPAY-ROLE')) { + config.headers.set('BOOTPAY-ROLE', this.$role || 'user') + } const authorization = this.authorizationHeader() if (authorization) { diff --git a/src/lib/commerce/modules/index.ts b/src/lib/commerce/modules/index.ts index 5587cc8..8c13126 100644 --- a/src/lib/commerce/modules/index.ts +++ b/src/lib/commerce/modules/index.ts @@ -12,5 +12,6 @@ export * from './category' export * from './coupon' export * from './point' export * from './cart' +export * from './mall-setting' export * from './store' diff --git a/src/lib/commerce/modules/mall-setting.ts b/src/lib/commerce/modules/mall-setting.ts new file mode 100644 index 0000000..c6ab3d7 --- /dev/null +++ b/src/lib/commerce/modules/mall-setting.ts @@ -0,0 +1,70 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { CommerceMallSetting, MallSettingUpdateParams } from '../types' +import { randomUUID } from 'crypto' + +export class MallSettingModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 몰 설정 조회 + * GET /v1/mall-setting + * supervisor scope 토큰 전용 + */ + async getMallSetting(idempotencyKey?: string): Promise> { + return this.bootpay.get('mall-setting', { + headers: this.supervisorHeaders(idempotencyKey) + }) + } + + async detail(idempotencyKey?: string): Promise> { + return this.getMallSetting(idempotencyKey) + } + + /** + * 몰 설정 수정 + * PUT /v1/mall-setting + * supervisor scope 토큰 전용 + * 요청 바디는 flatten 형식이며 전달된 값(non-null)만 서버로 전송된다. + * @param params 수정할 설정값 + * @param idempotencyKey 미지정시 자동 생성 + */ + async updateMallSetting( + params: MallSettingUpdateParams, + idempotencyKey?: string + ): Promise> { + return this.bootpay.put('mall-setting', this.compact(params), { + headers: this.supervisorHeaders(idempotencyKey) + }) + } + + async update( + params: MallSettingUpdateParams, + idempotencyKey?: string + ): Promise> { + return this.updateMallSetting(params, idempotencyKey) + } + + /** + * null/undefined 값을 제거한다. (Ruby SDK 의 payload.compact 와 동일 동작) + */ + private compact(params: MallSettingUpdateParams): Record { + return Object.fromEntries( + Object.entries(params || {}).filter(([, value]) => value !== undefined && value !== null) + ) + } + + /** + * supervisor 전용 요청 헤더 + * Idempotency-Key 는 미지정시 매 호출마다 생성된다. + */ + private supervisorHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'supervisor' + } + } +} diff --git a/src/lib/commerce/modules/order-subscription.ts b/src/lib/commerce/modules/order-subscription.ts index ead3b4f..dc72805 100644 --- a/src/lib/commerce/modules/order-subscription.ts +++ b/src/lib/commerce/modules/order-subscription.ts @@ -11,8 +11,13 @@ import { SupervisorOrderSubscriptionRejectParams, SupervisorOrderSubscriptionTerminateParams, SupervisorOrderSubscriptionPauseParams, - SupervisorOrderSubscriptionResumeParams + SupervisorOrderSubscriptionResumeParams, + SupervisorOrderSubscriptionChargeParams, + SupervisorOrderSubscriptionChargeRevokeParams, + OrderSubscriptionChargeResponse, + OrderSubscriptionChargeRevokeResponse } from '../types' +import { randomUUID } from 'crypto' export class OrderSubscriptionRequestIngModule { private bootpay: BootpayCommerceResource @@ -166,4 +171,55 @@ export class OrderSubscriptionModule { ): Promise> { return this.bootpay.put(`order_subscriptions/${orderSubscriptionId}/resume`, params) } + + /** + * 수시결제(온디맨드) charge_key 즉시 결제 + * POST /v1/order_subscriptions/charge + * charge_key 는 body 로만 전송한다 (URL/query 금지 — 액세스 로그 노출 방지) + * @param params 결제 파라미터 + */ + async supervisorCharge( + params: SupervisorOrderSubscriptionChargeParams + ): Promise> { + const { idempotency_key, ...payload } = params + return this.bootpay.post('order_subscriptions/charge', this.compact(payload), { + headers: this.supervisorHeaders(idempotency_key) + }) + } + + /** + * 수시결제(온디맨드) charge_key 해지 + * DELETE /v1/order_subscriptions/charge + * 해지 이후 해당 키로의 재결제는 불가능하다 + * @param params 해지 파라미터 + */ + async supervisorChargeRevoke( + params: SupervisorOrderSubscriptionChargeRevokeParams + ): Promise> { + const { idempotency_key, ...payload } = params + return this.bootpay.delete('order_subscriptions/charge', { + data: this.compact(payload), + headers: this.supervisorHeaders(idempotency_key) + }) + } + + /** + * null/undefined 값을 제거한다. (Ruby SDK 의 payload.compact 와 동일 동작) + */ + private compact(payload: Record): Record { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null) + ) + } + + /** + * supervisor 전용 요청 헤더 + * Idempotency-Key 는 미지정시 매 호출마다 생성된다. + */ + private supervisorHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'supervisor' + } + } } diff --git a/src/lib/commerce/types/index.ts b/src/lib/commerce/types/index.ts index deb8d43..68dab5a 100644 --- a/src/lib/commerce/types/index.ts +++ b/src/lib/commerce/types/index.ts @@ -13,3 +13,4 @@ export * from './category' export * from './coupon' export * from './point' export * from './cart' +export * from './mall-setting' diff --git a/src/lib/commerce/types/mall-setting.ts b/src/lib/commerce/types/mall-setting.ts new file mode 100644 index 0000000..9b7da58 --- /dev/null +++ b/src/lib/commerce/types/mall-setting.ts @@ -0,0 +1,236 @@ +/** + * 몰 설정 (Mall Setting) + * 요청/응답 바디는 flatten 형식이며, 전달된 값(non-null)만 서버로 전송된다. + * supervisor scope 전용 endpoint. + */ +export interface MallSettingUpdateParams { + // 위젯 + normal_widget_key?: string + subscription_widget_key?: string + + // 사업자 정보 + seller_name?: string + seller_name_en?: string + biz_email?: string + biz_tel?: string + biz_fax?: string + registration_no?: string + corp_reg_no?: string + mail_order_sales_number?: string + owner_name?: string + zip?: string + addr_1?: string + addr_2?: string + privacy_name?: string + privacy_email?: string + + // 몰 기본 정보 + name?: string + description?: string + status?: number + invoice_title?: string + + // 브랜딩 + use_logo?: boolean + logo?: string + use_favicon?: boolean + favicon?: string + use_open_graph?: boolean + og_image?: string + use_signature?: boolean + signature?: string + + // 고객센터 운영시간 + use_operation_time?: boolean + customer_service_center_operation_time?: string + rest_start_hour?: number + rest_start_minute?: number + rest_end_hour?: number + rest_end_minute?: number + /** 휴무일 (요일 코드 배열 또는 서버 정의 문자열) */ + rest_day?: any + hosting_service?: string + + // 주문/연령 정책 + use_non_member_order?: boolean + use_age_accept_19?: boolean + use_age_accept_14?: boolean + use_age_accept_parent_name?: boolean + use_age_accept_parent_birth?: boolean + use_age_accept_parent_email?: boolean + + // 회원가입 수집 항목 + use_membership_collect_phone?: boolean + use_membership_collect_tel?: boolean + use_membership_collect_email?: boolean + use_membership_collect_address?: boolean + use_membership_collect_bank?: boolean + use_membership_collect_birth?: boolean + use_membership_collect_gender?: boolean + use_membership_collect_interest?: boolean + membership_collect_interest_number?: number + use_membership_collect_customs?: boolean + use_membership_collect_nickname?: boolean + use_membership_collect_recommend_id?: boolean + recommend_id_point_to?: number + recommend_id_point_from?: number + use_membership_collect_business?: boolean + use_membership_collect_register?: boolean + membership_only_business?: boolean + + // 기업(그룹) 회원 + use_corporate_department?: boolean + sub_group_type?: number + use_corporate_signup_approval?: boolean + /** 기업 회원 허용 이메일 도메인 목록 */ + corporate_email_domains?: any + use_corporate_auto_approve?: boolean + use_corporate_invite_only?: boolean + + // 회원 정보 노출 항목 + use_member_info_phone?: boolean + use_member_info_tel?: boolean + use_member_info_email?: boolean + use_member_info_address?: boolean + use_member_info_bank?: boolean + use_member_info_birth?: boolean + use_member_info_gender?: boolean + use_member_info_customs?: boolean + use_member_info_nickname?: boolean + use_member_info_register?: boolean + + // 주문자 수집 항목 + orderer_collect_phone?: boolean + orderer_collect_tel?: boolean + orderer_collect_email?: boolean + + // 주문/취소 정책 + order_prefix?: string + use_order_cancel?: boolean + /** 취소 승인 사용 여부 (서버 필드명 오타 그대로 유지) */ + use_oder_cancel_approval?: boolean + /** 취소 사유 목록 */ + order_cancel_reasons?: any + order_cancel_reason_required_type?: number + order_cancel_request_message?: string + order_cancel_done_message?: string + + // 회원 가입/인증 방식 + use_general_membership?: boolean + general_membership_duplication?: number + use_certification?: boolean + certification_type?: number + general_membership_id_type?: number + use_membership_duplication_email?: boolean + use_membership_duplication_phone?: boolean + use_social_membership?: boolean + /** 사용 소셜 로그인 타입 */ + social_membership_type?: any + + // 적립금 + use_point?: boolean + use_point_transaction?: boolean + point_display_name?: string + point_min_balance?: number + /** 적립 제외 조건 */ + point_not_condition?: any + /** 적립 조건 */ + point_condition?: any + use_point_max_rate?: boolean + point_max_rate?: number + use_point_max_amount?: boolean + point_max_amount?: number + point_rate?: number + point_calc_type1?: number + point_calc_type2?: number + use_point_advance_discount?: boolean + point_advance_discount_rate?: number + use_point_expire?: boolean + point_expire_type?: number + point_issue_event_type?: number + point_issue_delay_days?: number + + // 오픈마켓 / 상품 + use_open_market?: boolean + use_product_approval?: boolean + use_product_review?: boolean + use_product_review_point?: boolean + product_review_point?: number + product_review_photo_point?: number + use_product_review_answer?: boolean + use_product_review_auto_answer?: boolean + product_review_auto_answer_minute?: number + product_review_auto_answer_text?: string + use_product_qna?: boolean + product_qna_member_auth?: number + use_product_qna_answer_option?: boolean + + // 게시판 / 상담 + use_notice?: boolean + use_qna?: boolean + use_faq?: boolean + use_chat_support?: boolean + chat_support_type?: number + chat_support_key?: string + + // 휴면 / 탈퇴 + use_dormant?: boolean + dormant_year?: number + dormant_restore?: number + use_withdrawal?: boolean + use_withdrawal_guide_message?: boolean + use_withdrawal_guide_message_after?: boolean + withdrawal_guide_message_after?: string + use_withdrawal_auto?: boolean + withdrawal_auto_year?: number + + // 정기구독 정산 + use_subscription_aggregate_transaction?: boolean + subscription_month_day?: number + subscription_week_day?: number + + // 구매 한도 + use_limit?: boolean + limit_month_purchase?: number + limit_week_purchase?: number + use_limit_payment?: boolean + use_limit_message?: boolean + + // 약관 + terms_of_service?: string + terms_of_privacy_policy?: string + terms_of_privacy_collect?: string + terms_of_privacy_third?: string + + // 결제 / 노출 + payment_timeout?: number + product_sort_type?: number + mall_theme_type?: number + catalog_display_type?: number + catalog_headline?: string + catalog_bg_color?: string + catalog_view_type_pc?: number + catalog_view_type_mobile?: number + catalog_product_sort_type?: number + + // 장바구니 / 위시리스트 + use_cart?: boolean + cart_storage_period?: number + cart_max_limit?: number + cart_add_action?: number + cart_direct_purchase?: boolean + cart_option_change?: boolean + cart_discount_display?: boolean + use_wishlist?: boolean + wishlist_max_limit?: number + cart_wishlist_display?: boolean +} + +export interface CommerceMallSetting extends MallSettingUpdateParams { + mall_setting_id?: string + project_id?: string + seller_id?: string + created_at?: string + updated_at?: string + [key: string]: unknown +} diff --git a/src/lib/commerce/types/order-subscription.ts b/src/lib/commerce/types/order-subscription.ts index f523d9b..f99dfaa 100644 --- a/src/lib/commerce/types/order-subscription.ts +++ b/src/lib/commerce/types/order-subscription.ts @@ -126,3 +126,45 @@ export interface SupervisorOrderSubscriptionPauseParams { export interface SupervisorOrderSubscriptionResumeParams { reason?: string } + +/** + * 수시결제(온디맨드) charge_key 즉시 결제 파라미터 + * charge_key 는 body 로만 전송된다 (URL/query 금지 — 액세스 로그 노출 방지) + */ +export interface SupervisorOrderSubscriptionChargeParams { + charge_key: string + price: number + tax_free_price?: number + user?: Record + metadata?: Record + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string +} + +/** + * 수시결제(온디맨드) charge_key 해지 파라미터 + */ +export interface SupervisorOrderSubscriptionChargeRevokeParams { + charge_key: string + user?: Record + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string +} + +export interface OrderSubscriptionChargeResponse { + order_id?: string + order_number?: string + receipt_id?: string + charge_key?: string + price?: number + tax_free_price?: number + status?: number + [key: string]: unknown +} + +export interface OrderSubscriptionChargeRevokeResponse { + charge_key?: string + revoked_at?: string + status?: number + [key: string]: unknown +} diff --git a/test/commerce/mallSettingDetail.js b/test/commerce/mallSettingDetail.js new file mode 100644 index 0000000..b05b957 --- /dev/null +++ b/test/commerce/mallSettingDetail.js @@ -0,0 +1,25 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); +// Commerce API - 몰 설정 조회 테스트 +// +// GET /v1/mall-setting (supervisor scope 전용) + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode + }) + + try { + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() + + const response = await commerce.mallSetting.getMallSetting() + console.log('Mall Setting:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/mallSettingRequest.js b/test/commerce/mallSettingRequest.js new file mode 100644 index 0000000..704b106 --- /dev/null +++ b/test/commerce/mallSettingRequest.js @@ -0,0 +1,78 @@ +const assert = require('assert'); +const { BootpayCommerce } = require('../../dist/bootpay-commerce.js'); + +function header(config, name) { + if (!config.headers) return undefined; + if (typeof config.headers.get === 'function') return config.headers.get(name); + return config.headers[name] || config.headers[name.toLowerCase()] || config.headers[name.toUpperCase()]; +} + +function body(config) { + return typeof config.data === 'string' ? JSON.parse(config.data) : config.data; +} + +// Commerce API - 몰 설정 요청 규약 테스트 (네트워크 호출 없음) +// 1) 조회: GET mall-setting +// 2) 수정: PUT mall-setting, null/undefined 값은 전송하지 않는다 (Ruby SDK 의 payload.compact 동작) +// 3) supervisor role + Idempotency-Key 헤더 + +(async () => { + const commerce = new BootpayCommerce({ + client_key: 'ck', + secret_key: 'sk', + mode: 'development' + }); + + const requests = []; + commerce.$http.defaults.adapter = async (config) => { + requests.push(config); + return { + data: {}, + status: 200, + statusText: 'OK', + headers: {}, + config, + request: {} + }; + }; + + // 1) 몰 설정 조회 + await commerce.mallSetting.getMallSetting(); + assert.strictEqual(requests[0].method.toLowerCase(), 'get'); + assert.strictEqual(requests[0].url, 'https://dev-api.bootapi.com/v1/mall-setting'); + assert.strictEqual(header(requests[0], 'BOOTPAY-ROLE'), 'supervisor'); + assert.ok(header(requests[0], 'Idempotency-Key'), 'Idempotency-Key header is required'); + + // 2) detail() 은 getMallSetting() 의 alias + await commerce.mallSetting.detail(); + assert.strictEqual(requests[1].url, 'https://dev-api.bootapi.com/v1/mall-setting'); + + // 3) 몰 설정 수정 — 전달한 값만 flatten 바디로 전송 + await commerce.mallSetting.updateMallSetting({ + name: '테스트몰', + use_cart: true, + cart_max_limit: 100, + point_rate: 0, + description: undefined, + og_image: null + }); + assert.strictEqual(requests[2].method.toLowerCase(), 'put'); + assert.strictEqual(requests[2].url, 'https://dev-api.bootapi.com/v1/mall-setting'); + assert.deepStrictEqual(body(requests[2]), { + name: '테스트몰', + use_cart: true, + cart_max_limit: 100, + point_rate: 0 + }); + assert.strictEqual(header(requests[2], 'BOOTPAY-ROLE'), 'supervisor'); + + // 4) idempotency key 직접 지정 + await commerce.mallSetting.update({ name: '테스트몰' }, 'my-idempotency-key'); + assert.strictEqual(header(requests[3], 'Idempotency-Key'), 'my-idempotency-key'); + assert.deepStrictEqual(body(requests[3]), { name: '테스트몰' }); + + console.log('commerce mall setting: supervisor role, flatten payload compaction'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/commerce/mallSettingUpdate.js b/test/commerce/mallSettingUpdate.js new file mode 100644 index 0000000..09abd80 --- /dev/null +++ b/test/commerce/mallSettingUpdate.js @@ -0,0 +1,33 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); +// Commerce API - 몰 설정 수정 테스트 +// +// PUT /v1/mall-setting (supervisor scope 전용) +// 요청 바디는 flatten 형식이며, 전달한 값(non-null)만 서버로 전송된다. + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode + }) + + try { + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() + + const response = await commerce.mallSetting.updateMallSetting({ + name: '부트페이 테스트몰', + description: '몰 설정 수정 테스트', + use_cart: true, + cart_max_limit: 100, + use_point: true, + point_rate: 1 + }) + console.log('Mall Setting Update:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionCharge.js b/test/commerce/orderSubscriptionCharge.js new file mode 100644 index 0000000..382141f --- /dev/null +++ b/test/commerce/orderSubscriptionCharge.js @@ -0,0 +1,32 @@ +const { getCommerceKeys, COMMERCE_TEST_DATA } = require('../config.js'); +const keys = getCommerceKeys(); +// Commerce API - 수시결제(온디맨드) charge_key 즉시 결제 테스트 +// +// POST /v1/order_subscriptions/charge (supervisor 전용) +// charge_key 는 body 로만 전송된다 (URL/query 금지 — 액세스 로그 노출 방지) + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode + }) + + try { + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() + + const response = await commerce.asSupervisor().orderSubscription.supervisorCharge({ + charge_key: COMMERCE_TEST_DATA.charge_key, + price: 1000, + tax_free_price: 0, + user: { id: COMMERCE_TEST_DATA.user_id }, + metadata: { memo: 'charge key 즉시 결제 테스트' } + }) + console.log('OrderSubscription Charge Response:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionChargeRequest.js b/test/commerce/orderSubscriptionChargeRequest.js new file mode 100644 index 0000000..bcf1b87 --- /dev/null +++ b/test/commerce/orderSubscriptionChargeRequest.js @@ -0,0 +1,107 @@ +const assert = require('assert'); +const { BootpayCommerce } = require('../../dist/bootpay-commerce.js'); + +function header(config, name) { + if (!config.headers) return undefined; + if (typeof config.headers.get === 'function') return config.headers.get(name); + return config.headers[name] || config.headers[name.toLowerCase()] || config.headers[name.toUpperCase()]; +} + +function body(config) { + return typeof config.data === 'string' ? JSON.parse(config.data) : config.data; +} + +// Commerce API - 수시결제(온디맨드) charge_key 요청 규약 테스트 (네트워크 호출 없음) +// 1) 결제: POST order_subscriptions/charge, charge_key 는 body 로만 전송 +// 2) 해지: DELETE order_subscriptions/charge, body 에 charge_key/user +// 3) supervisor role + Idempotency-Key 헤더 (미지정시 매 호출마다 생성) + +(async () => { + const commerce = new BootpayCommerce({ + client_key: 'ck', + secret_key: 'sk', + mode: 'development' + }); + + const requests = []; + commerce.$http.defaults.adapter = async (config) => { + requests.push(config); + return { + data: {}, + status: 200, + statusText: 'OK', + headers: {}, + config, + request: {} + }; + }; + + // 1) charge_key 즉시 결제 + await commerce.orderSubscription.supervisorCharge({ + charge_key: 'charge_key_1', + price: 1000, + tax_free_price: 0, + user: { id: 'user_1' }, + metadata: { memo: 'test' } + }); + assert.strictEqual(requests[0].method.toLowerCase(), 'post'); + assert.strictEqual(requests[0].url, 'https://dev-api.bootapi.com/v1/order_subscriptions/charge'); + assert.ok(!requests[0].url.includes('charge_key_1'), 'charge_key must never appear in the URL'); + assert.deepStrictEqual(body(requests[0]), { + charge_key: 'charge_key_1', + price: 1000, + tax_free_price: 0, + user: { id: 'user_1' }, + metadata: { memo: 'test' } + }); + assert.strictEqual(header(requests[0], 'BOOTPAY-ROLE'), 'supervisor'); + assert.ok(header(requests[0], 'Idempotency-Key'), 'Idempotency-Key header is required'); + + // 2) 호출마다 Idempotency-Key 는 새로 생성된다 + await commerce.orderSubscription.supervisorCharge({ charge_key: 'charge_key_1', price: 1000 }); + assert.notStrictEqual(header(requests[1], 'Idempotency-Key'), header(requests[0], 'Idempotency-Key')); + assert.deepStrictEqual(body(requests[1]), { charge_key: 'charge_key_1', price: 1000 }); + + // 3) idempotency_key 를 직접 지정하면 헤더로만 전송되고 body 에는 포함되지 않는다 + await commerce.orderSubscription.supervisorCharge({ + charge_key: 'charge_key_1', + price: 1000, + idempotency_key: 'my-idempotency-key' + }); + assert.strictEqual(header(requests[2], 'Idempotency-Key'), 'my-idempotency-key'); + assert.deepStrictEqual(body(requests[2]), { charge_key: 'charge_key_1', price: 1000 }); + + // 4) charge_key 해지 — DELETE + body + await commerce.orderSubscription.supervisorChargeRevoke({ + charge_key: 'charge_key_1', + user: { id: 'user_1' } + }); + assert.strictEqual(requests[3].method.toLowerCase(), 'delete'); + assert.strictEqual(requests[3].url, 'https://dev-api.bootapi.com/v1/order_subscriptions/charge'); + assert.deepStrictEqual(body(requests[3]), { charge_key: 'charge_key_1', user: { id: 'user_1' } }); + assert.strictEqual(header(requests[3], 'BOOTPAY-ROLE'), 'supervisor'); + assert.ok(header(requests[3], 'Idempotency-Key'), 'Idempotency-Key header is required'); + + // 5) role 을 user 로 두어도 charge endpoint 는 supervisor 로 나간다 + commerce.asUser(); + await commerce.orderSubscription.supervisorCharge({ charge_key: 'charge_key_1', price: 1000 }); + assert.strictEqual(header(requests[4], 'BOOTPAY-ROLE'), 'supervisor'); + + // 6) 일반 endpoint 는 설정된 role 을 그대로 사용한다 + await commerce.orderSubscription.list(); + assert.strictEqual(header(requests[5], 'BOOTPAY-ROLE'), 'user'); + + // 7) null/undefined 값은 body 에서 제거된다 (Ruby SDK 의 payload.compact 와 동일) + await commerce.orderSubscription.supervisorCharge({ + charge_key: 'charge_key_1', + price: 1000, + tax_free_price: null, + user: undefined + }); + assert.deepStrictEqual(body(requests[6]), { charge_key: 'charge_key_1', price: 1000 }); + + console.log('commerce order subscription charge: body-only charge_key, supervisor role, idempotency key'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/commerce/orderSubscriptionChargeRevoke.js b/test/commerce/orderSubscriptionChargeRevoke.js new file mode 100644 index 0000000..012e156 --- /dev/null +++ b/test/commerce/orderSubscriptionChargeRevoke.js @@ -0,0 +1,29 @@ +const { getCommerceKeys, COMMERCE_TEST_DATA } = require('../config.js'); +const keys = getCommerceKeys(); +// Commerce API - 수시결제(온디맨드) charge_key 해지 테스트 +// +// DELETE /v1/order_subscriptions/charge (supervisor 전용) +// 해지 이후 해당 키로의 재결제는 불가능하다 + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode + }) + + try { + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() + + const response = await commerce.asSupervisor().orderSubscription.supervisorChargeRevoke({ + charge_key: COMMERCE_TEST_DATA.charge_key, + user: { id: COMMERCE_TEST_DATA.user_id } + }) + console.log('OrderSubscription Charge Revoke Response:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/config.js b/test/config.js index 4b3bed8..9ea6710 100644 --- a/test/config.js +++ b/test/config.js @@ -94,7 +94,9 @@ const TEST_DATA = { reserve_id: '6490149ca575b40024f0b70d', reserve_id_2: '628b316cd01c7e00219b6081', user_id: '1234', - certificate_receipt_id: '69fd7187564d1f550535538c' + certificate_receipt_id: '69fd7187564d1f550535538c', + // 우선순위(순차) 결제 빌링키 조회용 위젯키 — .env 의 BOOTPAY_TEST_PG_WIDGET_KEY 로 주입 + widget_key: env('BOOTPAY_TEST_PG_WIDGET_KEY', 'WIDGET_KEY_HERE') }; // Commerce 테스트 fixture — placeholder 였던 ID 들을 .env 로 주입. @@ -114,6 +116,7 @@ const COMMERCE_TEST_DATA = { order_subscription_adjustment_id: env('BOOTPAY_TEST_COMMERCE_ORDER_SUBSCRIPTION_ADJUSTMENT_ID', 'ORDER_SUBSCRIPTION_ADJUSTMENT_ID_HERE'), order_cancel_request_history_id: env('BOOTPAY_TEST_COMMERCE_ORDER_CANCEL_REQUEST_HISTORY_ID', 'ORDER_CANCEL_REQUEST_HISTORY_ID_HERE'), stand_id: env('BOOTPAY_TEST_COMMERCE_STAND_ID', 'STAND_ID_HERE'), + charge_key: env('BOOTPAY_TEST_COMMERCE_CHARGE_KEY', 'CHARGE_KEY_HERE'), keyword: env('BOOTPAY_TEST_COMMERCE_KEYWORD', '테스트'), s_at: env('BOOTPAY_TEST_COMMERCE_S_AT', '2024-01-01'), e_at: env('BOOTPAY_TEST_COMMERCE_E_AT', '2099-12-31') diff --git a/test/pg/lookupSequentialBillingKey.js b/test/pg/lookupSequentialBillingKey.js new file mode 100644 index 0000000..2bbc8ab --- /dev/null +++ b/test/pg/lookupSequentialBillingKey.js @@ -0,0 +1,16 @@ +const { Bootpay } = require('../../dist/bootpay.js'); +const { getActivePgConfig, TEST_DATA } = require('../config.js'); +// PG API - 우선순위(순차) 결제 빌링키 조회 +// GET /v2/subscribe/sequential_billing_key/{billing_key}?widget_key={widget_key} + +(async () => { + Bootpay.setConfiguration(getActivePgConfig()); + try { + // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. + await Bootpay.getAccessToken(); + const response = await Bootpay.lookupSequentialBillingKey(TEST_DATA.widget_key, TEST_DATA.billing_key_2); + console.log(response); + } catch (e) { + console.log(e); + } +})(); diff --git a/test/pg/lookupSequentialBillingKeyRequest.js b/test/pg/lookupSequentialBillingKeyRequest.js new file mode 100644 index 0000000..efa0fb9 --- /dev/null +++ b/test/pg/lookupSequentialBillingKeyRequest.js @@ -0,0 +1,34 @@ +const assert = require('assert'); +const { Bootpay } = require('../../dist/bootpay.js'); + +// PG API - 우선순위(순차) 결제 빌링키 조회 URL 규약 테스트 (네트워크 호출 없음) +// GET subscribe/sequential_billing_key/{billing_key}?widget_key={widget_key} + +(async () => { + Bootpay.setConfiguration({ client_key: 'ck', secret_key: 'sk', mode: 'development' }); + + const requests = []; + Bootpay.$http.defaults.adapter = async (config) => { + requests.push(config); + return { + data: {}, + status: 200, + statusText: 'OK', + headers: {}, + config, + request: {} + }; + }; + + await Bootpay.lookupSequentialBillingKey('widget_key_1', 'billing_key_1'); + assert.strictEqual(requests[0].method.toLowerCase(), 'get'); + assert.strictEqual( + requests[0].url, + 'https://dev-api.bootpay.co.kr/v2/subscribe/sequential_billing_key/billing_key_1?widget_key=widget_key_1' + ); + + console.log('pg lookupSequentialBillingKey: billing_key in path, widget_key in query'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/test.md b/test/test.md index 24a0e8d..312b131 100644 --- a/test/test.md +++ b/test/test.md @@ -40,6 +40,12 @@ node test/pg/lookupSubscribeBilling.js # 빌링키 조회 (billing_key) node test/pg/lookupBilling.js +# 우선순위(순차) 결제 빌링키 조회 (widget_key + billing_key) +node test/pg/lookupSequentialBillingKey.js + +# 우선순위 빌링키 조회 URL 규약 검증 (네트워크 호출 없음, 키 불필요) +node test/pg/lookupSequentialBillingKeyRequest.js + # 빌링키 삭제 node test/pg/destroySubscribeBillingKey.js @@ -69,6 +75,18 @@ node test/commerce/[테스트파일].js # Authorization 헤더 선택 규칙 검증 (네트워크 호출 없음, 키 불필요) node test/commerce/authorizationHeader.js + +# 수시결제(온디맨드) charge_key 즉시 결제 / 해지 (supervisor 전용) +node test/commerce/orderSubscriptionCharge.js +node test/commerce/orderSubscriptionChargeRevoke.js + +# 몰 설정 조회 / 수정 (supervisor 전용) +node test/commerce/mallSettingDetail.js +node test/commerce/mallSettingUpdate.js + +# charge_key / 몰 설정 요청 규약 검증 (네트워크 호출 없음, 키 불필요) +node test/commerce/orderSubscriptionChargeRequest.js +node test/commerce/mallSettingRequest.js ``` ## 테스트 데이터 From 6485275b54187fb7b56f347884efb3ca07f71f7e Mon Sep 17 00:00:00 2001 From: Bootpay SDK Bot Date: Fri, 14 Aug 2026 14:25:50 +0000 Subject: [PATCH 116/117] =?UTF-8?q?sync:=20817dbe80=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source: git.bootpay.co.kr/bootpay-sdk/backend-ruby@817dbe804b30af68817b912c2fde28b787040ff2 --- CHANGELOG.md | 13 +++ README.md | 44 ++++++++ package.json | 2 +- src/lib/commerce/modules/product.ts | 57 ++++++++-- src/lib/commerce/modules/store.ts | 33 ++++-- src/lib/commerce/modules/user.ts | 104 ++++++++++++++++-- src/lib/commerce/types/product.ts | 11 ++ src/lib/commerce/types/user.ts | 46 ++++++++ test/commerce/productMallRequest.js | 82 ++++++++++++++ test/commerce/storeRequest.js | 61 +++++++++++ test/commerce/userMallSessionRequest.js | 139 ++++++++++++++++++++++++ test/test.md | 11 ++ 12 files changed, 577 insertions(+), 26 deletions(-) create mode 100644 test/commerce/productMallRequest.js create mode 100644 test/commerce/storeRequest.js create mode 100644 test/commerce/userMallSessionRequest.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f4f8e4a..04f5964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +### 2.8.0 +* Commerce: 쇼핑몰(V1 Mall API) 회원 endpoint 정정 및 추가 — 단수형 `user/...` 경로 사용 (기존 `users/...` 외부 회원 연동 API 는 그대로 유지) + - `user.userLogin({ login_id, password, corporate_type })`: `POST user/login` — corporate_type 미지정시 0 + - `user.userSession(userJwt)`: `GET user/session` + - `user.userLogout(userJwt)`: `DELETE user/session` + - `user.userJoin({ login_id, password, name, ... })`: `POST user/join` — null/undefined 값은 전송하지 않음 + - `user.userJoinCheck(type, pk)`: `GET user/join/{type}?pk={pk}` + - 세션이 필요한 호출은 회원 JWT 를 `Bootpay-User-JWT` 헤더로 전달 (값이 있을 때만 부착) +* Commerce: 상품 조회 Mall API parity + - `product.products`: `GET products` — `page`/`limit` 기본값 1/20, `category_id`/`sort` 파라미터 및 `user_jwt` 지원 + - `product.productDetail(productId, userJwt)`: `GET products/{product_id}` — 회원 JWT 지원 +* Commerce: `store.getStore`/`getStoreDetail` 에 `Idempotency-Key` 헤더 부착 (`idempotencyKey` 인자로 직접 지정 가능) + ### 2.7.0 * PG: 우선순위(순차) 결제 빌링키 조회 `lookupSequentialBillingKey(widgetKey, billingKey)` 추가 — `GET subscribe/sequential_billing_key/{billing_key}?widget_key={widget_key}` * Commerce: 수시결제(온디맨드) charge_key 결제/해지 추가 (supervisor 전용) diff --git a/README.md b/README.md index 1fa934c..2505c9f 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용 - [10-5. 정기구독 관리](#10-5-정기구독-관리) - [10-6. 청구서 관리](#10-6-청구서-관리) - [10-7. 몰 설정 관리](#10-7-몰-설정-관리) + - [10-8. 쇼핑몰 회원 세션 관리](#10-8-쇼핑몰-회원-세션-관리) + - [10-9. 가맹점 정보 조회](#10-9-가맹점-정보-조회) - [Example 프로젝트](#example-프로젝트) - [Documentation](#documentation) - [기술문의](#기술문의) @@ -673,6 +675,48 @@ await commerce.mallSetting.updateMallSetting({ }) ``` +### 10-8. 쇼핑몰 회원 세션 관리 + +쇼핑몰(Mall) 회원 API 입니다. 단수형 `user/...` 경로를 사용하며, 외부 회원 연동용 `users/...` API(`user.login`, `user.join`, `user.checkExist`)와는 별개의 endpoint 입니다. + +```javascript +// 회원가입 — 전달한 값(non-null)만 서버로 전송되며, corporate_type 미지정시 0(개인) +await commerce.user.userJoin({ + login_id: 'test_user@example.com', + password: 'password123', + name: '테스트 사용자', + email: 'test_user@example.com', + phone: '010-1234-5678' +}) + +// 회원가입 중복 확인 — email-exist, id-exist, phone-exist, group-business-number-exist +await commerce.user.userJoinCheck('email-exist', 'test_user@example.com') + +// 로그인 +const login = await commerce.user.userLogin({ + login_id: 'test_user@example.com', + password: 'password123' +}) + +// 세션 조회 / 로그아웃 — 로그인시 발급받은 회원 JWT 를 Bootpay-User-JWT 헤더로 전달합니다 +await commerce.user.userSession(userJwt) +await commerce.user.userLogout(userJwt) + +// 회원 JWT 를 넘기면 상품 조회에도 회원 컨텍스트가 적용됩니다 +await commerce.product.products({ page: 1, limit: 20, category_id: 'CATEGORY_ID', user_jwt: userJwt }) +await commerce.product.productDetail('PRODUCT_ID', userJwt) +``` + +### 10-9. 가맹점 정보 조회 + +```javascript +// 가맹점 기본 정보 +const store = await commerce.store.getStore() + +// 가맹점 상세 정보 +const storeDetail = await commerce.store.getStoreDetail() +``` + 더 자세한 Commerce API 사용 예제는 [test/commerce](./test/commerce) 디렉토리를 참고해주세요. ## Example 프로젝트 diff --git a/package.json b/package.json index e614e26..d125f28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.7.0", + "version": "2.8.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", diff --git a/src/lib/commerce/modules/product.ts b/src/lib/commerce/modules/product.ts index ca10fad..b89ef09 100644 --- a/src/lib/commerce/modules/product.ts +++ b/src/lib/commerce/modules/product.ts @@ -1,8 +1,9 @@ import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' -import { CommerceProduct, ProductListParams, ProductStatusParams } from '../types' +import { CommerceProduct, ProductListParams, MallProductListParams, ProductStatusParams } from '../types' import FormData from 'form-data' import fs from 'fs' import path from 'path' +import { randomUUID } from 'crypto' export class ProductModule { private bootpay: BootpayCommerceResource @@ -33,10 +34,28 @@ export class ProductModule { /** - * 상품 목록 조회 (Mall API alias) + * 상품 목록 조회 (V1 Mall API) + * GET /v1/products + * page/limit 은 미지정시 각각 1 / 20 이 적용되고, 나머지 값은 지정된 것만 전송한다. + * @param params 조회 파라미터 */ - async products(params?: ProductListParams): Promise> { - return this.list(params) + async products(params?: MallProductListParams): Promise> { + const { user_jwt, idempotency_key, ...rest } = params || {} + const queryParams = new URLSearchParams() + queryParams.append('page', (rest.page === undefined ? 1 : rest.page).toString()) + queryParams.append('limit', (rest.limit === undefined ? 20 : rest.limit).toString()) + if (rest.category_id) queryParams.append('category_id', rest.category_id) + if (rest.sort) queryParams.append('sort', rest.sort) + if (rest.keyword) queryParams.append('keyword', rest.keyword) + if (rest.type !== undefined) queryParams.append('type', rest.type.toString()) + if (rest.period_type) queryParams.append('period_type', rest.period_type) + if (rest.s_at) queryParams.append('s_at', rest.s_at) + if (rest.e_at) queryParams.append('e_at', rest.e_at) + if (rest.category_code) queryParams.append('category_code', rest.category_code) + + return this.bootpay.get<{ items: CommerceProduct[]; total: number }>(`products?${queryParams.toString()}`, { + headers: this.mallHeaders(user_jwt, idempotency_key) + }) } /** @@ -87,10 +106,20 @@ export class ProductModule { } /** - * 상품 상세 조회 (Mall API alias) + * 상품 상세 조회 (V1 Mall API) + * GET /v1/products/{product_id} + * @param productId 상품 ID + * @param userJwt 회원 JWT (선택) + * @param idempotencyKey 미지정시 자동 생성 */ - async productDetail(productId: string): Promise> { - return this.detail(productId) + async productDetail( + productId: string, + userJwt?: string, + idempotencyKey?: string + ): Promise> { + return this.bootpay.get(`products/${productId}`, { + headers: this.mallHeaders(userJwt, idempotencyKey) + }) } /** @@ -122,4 +151,18 @@ export class ProductModule { async delete(productId: string): Promise> { return this.bootpay.delete(`products/${productId}`) } + + /** + * V1 Mall API 요청 헤더 + * Idempotency-Key 는 미지정시 매 호출마다 생성되고, Bootpay-User-JWT 는 값이 있을 때만 붙는다. + */ + private mallHeaders(userJwt?: string, idempotencyKey?: string): Record { + const headers: Record = { + 'Idempotency-Key': idempotencyKey || randomUUID() + } + if (userJwt !== undefined && userJwt !== null && userJwt !== '') { + headers['Bootpay-User-JWT'] = userJwt + } + return headers + } } diff --git a/src/lib/commerce/modules/store.ts b/src/lib/commerce/modules/store.ts index 71c112c..22ebd51 100644 --- a/src/lib/commerce/modules/store.ts +++ b/src/lib/commerce/modules/store.ts @@ -1,4 +1,5 @@ import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { randomUUID } from 'crypto' export class StoreModule { private bootpay: BootpayCommerceResource @@ -9,23 +10,39 @@ export class StoreModule { /** * 가맹점 기본 정보 조회 (/v1/store) + * @param idempotencyKey 미지정시 자동 생성 */ - async getStore(): Promise> { - return this.bootpay.get('store') + async getStore(idempotencyKey?: string): Promise> { + return this.bootpay.get('store', { + headers: this.storeHeaders(idempotencyKey) + }) } - async info(): Promise> { - return this.getStore() + async info(idempotencyKey?: string): Promise> { + return this.getStore(idempotencyKey) } /** * 가맹점 상세 정보 조회 (/v1/store/detail) + * @param idempotencyKey 미지정시 자동 생성 */ - async getStoreDetail(): Promise> { - return this.bootpay.get('store/detail') + async getStoreDetail(idempotencyKey?: string): Promise> { + return this.bootpay.get('store/detail', { + headers: this.storeHeaders(idempotencyKey) + }) } - async detail(): Promise> { - return this.getStoreDetail() + async detail(idempotencyKey?: string): Promise> { + return this.getStoreDetail(idempotencyKey) + } + + /** + * 가맹점 정보 조회 요청 헤더 + * Idempotency-Key 는 미지정시 매 호출마다 생성된다. + */ + private storeHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID() + } } } diff --git a/src/lib/commerce/modules/user.ts b/src/lib/commerce/modules/user.ts index ed094f9..2efebe4 100644 --- a/src/lib/commerce/modules/user.ts +++ b/src/lib/commerce/modules/user.ts @@ -1,5 +1,15 @@ import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' -import { CommerceUser, UserListParams, UserTokenResponse, UserLoginResponse } from '../types' +import { + CommerceUser, + UserListParams, + UserTokenResponse, + UserLoginResponse, + MallUserLoginParams, + MallUserJoinParams, + MallUserJoinCheckType, + MallUserSessionResponse +} from '../types' +import { randomUUID } from 'crypto' export class UserModule { private bootpay: BootpayCommerceResource @@ -55,24 +65,75 @@ export class UserModule { } /** - * 회원 로그인 (Mall API alias) + * 회원 로그인 (V1 Mall API) + * POST /v1/user/login + * @param params 로그인 파라미터 (corporate_type 미지정시 0) */ - async userLogin(loginId: string, loginPw: string): Promise> { - return this.login(loginId, loginPw) + async userLogin(params: MallUserLoginParams): Promise> { + const { idempotency_key, corporate_type, ...rest } = params + return this.bootpay.post( + 'user/login', + this.compact({ ...rest, corporate_type: corporate_type === undefined ? 0 : corporate_type }), + { headers: this.mallHeaders(undefined, idempotency_key) } + ) } /** - * 회원가입 (Mall API alias) + * 회원 세션 조회 (V1 Mall API) + * GET /v1/user/session + * @param userJwt 로그인시 발급받은 회원 JWT + * @param idempotencyKey 미지정시 자동 생성 */ - async userJoin(user: CommerceUser): Promise> { - return this.join(user) + async userSession( + userJwt?: string, + idempotencyKey?: string + ): Promise> { + return this.bootpay.get('user/session', { + headers: this.mallHeaders(userJwt, idempotencyKey) + }) + } + + /** + * 회원 로그아웃 (V1 Mall API) + * DELETE /v1/user/session + * @param userJwt 로그인시 발급받은 회원 JWT + * @param idempotencyKey 미지정시 자동 생성 + */ + async userLogout(userJwt: string, idempotencyKey?: string): Promise> { + return this.bootpay.delete('user/session', { + headers: this.mallHeaders(userJwt, idempotencyKey) + }) } /** - * 회원가입 중복 확인 (Mall API alias) + * 회원가입 (V1 Mall API) + * POST /v1/user/join + * @param params 회원가입 파라미터 (corporate_type 미지정시 0, 나머지 null/undefined 값은 전송하지 않는다) */ - async userJoinCheck(type: string, pk: string): Promise> { - return this.checkExist(type, pk) + async userJoin(params: MallUserJoinParams): Promise> { + const { idempotency_key, corporate_type, ...rest } = params + return this.bootpay.post( + 'user/join', + this.compact({ ...rest, corporate_type: corporate_type === undefined ? 0 : corporate_type }), + { headers: this.mallHeaders(undefined, idempotency_key) } + ) + } + + /** + * 회원가입 중복 확인 (V1 Mall API) + * GET /v1/user/join/{type}?pk={pk} + * @param type email-exist, id-exist, phone-exist, group-business-number-exist + * @param pk 중복 확인할 값 + * @param idempotencyKey 미지정시 자동 생성 + */ + async userJoinCheck( + type: MallUserJoinCheckType | string, + pk: string, + idempotencyKey?: string + ): Promise> { + return this.bootpay.get<{ exists: boolean }>(`user/join/${type}?pk=${encodeURIComponent(pk)}`, { + headers: this.mallHeaders(undefined, idempotencyKey) + }) } /** @@ -118,4 +179,27 @@ export class UserModule { async delete(userId: string): Promise> { return this.bootpay.delete(`users/${userId}`) } + + /** + * null/undefined 값을 제거한다. (Ruby SDK 의 payload.compact 와 동일 동작) + */ + private compact(payload: Record): Record { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null) + ) + } + + /** + * V1 Mall API 요청 헤더 + * Idempotency-Key 는 미지정시 매 호출마다 생성되고, Bootpay-User-JWT 는 값이 있을 때만 붙는다. + */ + private mallHeaders(userJwt?: string, idempotencyKey?: string): Record { + const headers: Record = { + 'Idempotency-Key': idempotencyKey || randomUUID() + } + if (userJwt !== undefined && userJwt !== null && userJwt !== '') { + headers['Bootpay-User-JWT'] = userJwt + } + return headers + } } diff --git a/src/lib/commerce/types/product.ts b/src/lib/commerce/types/product.ts index 6bfc85e..1dff080 100644 --- a/src/lib/commerce/types/product.ts +++ b/src/lib/commerce/types/product.ts @@ -136,6 +136,17 @@ export interface ProductListParams extends ListParams { category_code?: string } +/** + * 상품 목록 조회 파라미터 (V1 Mall API) + * GET /v1/products + */ +export interface MallProductListParams extends ProductListParams { + category_id?: string + sort?: string + user_jwt?: string + idempotency_key?: string +} + export interface ProductStatusParams { product_id: string status: number diff --git a/src/lib/commerce/types/user.ts b/src/lib/commerce/types/user.ts index 99558b6..83bef3d 100644 --- a/src/lib/commerce/types/user.ts +++ b/src/lib/commerce/types/user.ts @@ -81,3 +81,49 @@ export interface UserLoginResponse { expired_at?: string user?: CommerceUser } + +/** + * 회원 로그인 파라미터 (V1 Mall API) + * POST /v1/user/login + */ +export interface MallUserLoginParams { + login_id: string + password: string + // 0: 개인, 1: 사업자 + corporate_type?: number + idempotency_key?: string +} + +/** + * 회원가입 파라미터 (V1 Mall API) + * POST /v1/user/join + */ +export interface MallUserJoinParams { + login_id: string + password: string + name: string + email?: string + phone?: string + nickname?: string + gender?: number + birth?: string + // 0: 개인, 1: 사업자 + corporate_type?: number + group?: Record + idempotency_key?: string +} + +/** + * 회원가입 중복 확인 타입 (V1 Mall API) + * GET /v1/user/join/{type} + */ +export type MallUserJoinCheckType = 'email-exist' | 'id-exist' | 'phone-exist' | 'group-business-number-exist' + +/** + * 회원 세션 조회 응답 (V1 Mall API) + */ +export interface MallUserSessionResponse { + user?: CommerceUser + access_token?: string + expired_at?: string +} diff --git a/test/commerce/productMallRequest.js b/test/commerce/productMallRequest.js new file mode 100644 index 0000000..d34bc7a --- /dev/null +++ b/test/commerce/productMallRequest.js @@ -0,0 +1,82 @@ +const assert = require('assert'); +const { BootpayCommerce } = require('../../dist/bootpay-commerce.js'); + +function header(config, name) { + if (!config.headers) return undefined; + if (typeof config.headers.get === 'function') return config.headers.get(name); + return config.headers[name] || config.headers[name.toLowerCase()] || config.headers[name.toUpperCase()]; +} + +// Commerce API - 쇼핑몰(V1 Mall API) 상품 요청 규약 테스트 (네트워크 호출 없음) +// 1) 목록: GET products (page/limit 기본값 1/20, category_id/sort/keyword 는 지정된 것만) +// 2) 상세: GET products/{product_id} +// 3) 두 endpoint 모두 Idempotency-Key + (있을 때만) Bootpay-User-JWT 헤더를 붙인다 + +(async () => { + const commerce = new BootpayCommerce({ + client_key: 'ck', + secret_key: 'sk', + mode: 'development' + }); + + const requests = []; + commerce.$http.defaults.adapter = async (config) => { + requests.push(config); + return { + data: {}, + status: 200, + statusText: 'OK', + headers: {}, + config, + request: {} + }; + }; + + // 1) 파라미터 없이 호출하면 page=1, limit=20 이 기본으로 붙는다 + await commerce.product.products(); + assert.strictEqual(requests[0].method.toLowerCase(), 'get'); + assert.strictEqual(requests[0].url, 'https://dev-api.bootapi.com/v1/products?page=1&limit=20'); + assert.ok(header(requests[0], 'Idempotency-Key'), 'Idempotency-Key header is required'); + assert.strictEqual(header(requests[0], 'Bootpay-User-JWT'), undefined); + + // 2) category_id / sort / keyword + 회원 JWT + await commerce.product.products({ + page: 2, + limit: 5, + category_id: 'CATEGORY_ID', + sort: 'created_at', + keyword: '테스트', + user_jwt: 'USER_JWT', + idempotency_key: 'products-key' + }); + assert.strictEqual( + requests[1].url, + 'https://dev-api.bootapi.com/v1/products?page=2&limit=5&category_id=CATEGORY_ID&sort=created_at&keyword=%ED%85%8C%EC%8A%A4%ED%8A%B8' + ); + assert.strictEqual(header(requests[1], 'Bootpay-User-JWT'), 'USER_JWT'); + assert.strictEqual(header(requests[1], 'Idempotency-Key'), 'products-key'); + + // 3) 상품 상세 — 회원 JWT 없이 + await commerce.product.productDetail('PRODUCT_ID'); + assert.strictEqual(requests[2].method.toLowerCase(), 'get'); + assert.strictEqual(requests[2].url, 'https://dev-api.bootapi.com/v1/products/PRODUCT_ID'); + assert.ok(header(requests[2], 'Idempotency-Key'), 'Idempotency-Key header is required'); + assert.strictEqual(header(requests[2], 'Bootpay-User-JWT'), undefined); + + // 4) 상품 상세 — 회원 JWT 지정 + await commerce.product.productDetail('PRODUCT_ID', 'USER_JWT', 'detail-key'); + assert.strictEqual(header(requests[3], 'Bootpay-User-JWT'), 'USER_JWT'); + assert.strictEqual(header(requests[3], 'Idempotency-Key'), 'detail-key'); + + // 5) 기존 list() / detail() 은 그대로 유지된다 (기본 page/limit 없음) + await commerce.product.list({ page: 1, limit: 10, type: 0 }); + assert.strictEqual(requests[4].url, 'https://dev-api.bootapi.com/v1/products?page=1&limit=10&type=0'); + + await commerce.product.detail('PRODUCT_ID'); + assert.strictEqual(requests[5].url, 'https://dev-api.bootapi.com/v1/products/PRODUCT_ID'); + + console.log('commerce mall product: products/product detail params + JWT header'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/commerce/storeRequest.js b/test/commerce/storeRequest.js new file mode 100644 index 0000000..ffbc87a --- /dev/null +++ b/test/commerce/storeRequest.js @@ -0,0 +1,61 @@ +const assert = require('assert'); +const { BootpayCommerce } = require('../../dist/bootpay-commerce.js'); + +function header(config, name) { + if (!config.headers) return undefined; + if (typeof config.headers.get === 'function') return config.headers.get(name); + return config.headers[name] || config.headers[name.toLowerCase()] || config.headers[name.toUpperCase()]; +} + +// Commerce API - 가맹점 정보 요청 규약 테스트 (네트워크 호출 없음) +// 1) 기본 정보: GET store +// 2) 상세 정보: GET store/detail +// 3) 두 endpoint 모두 Idempotency-Key 헤더를 붙인다 + +(async () => { + const commerce = new BootpayCommerce({ + client_key: 'ck', + secret_key: 'sk', + mode: 'development' + }); + + const requests = []; + commerce.$http.defaults.adapter = async (config) => { + requests.push(config); + return { + data: {}, + status: 200, + statusText: 'OK', + headers: {}, + config, + request: {} + }; + }; + + // 1) 가맹점 기본 정보 + await commerce.store.getStore(); + assert.strictEqual(requests[0].method.toLowerCase(), 'get'); + assert.strictEqual(requests[0].url, 'https://dev-api.bootapi.com/v1/store'); + assert.ok(header(requests[0], 'Idempotency-Key'), 'Idempotency-Key header is required'); + + // 2) info() 는 getStore() 의 alias + await commerce.store.info('store-key'); + assert.strictEqual(requests[1].url, 'https://dev-api.bootapi.com/v1/store'); + assert.strictEqual(header(requests[1], 'Idempotency-Key'), 'store-key'); + + // 3) 가맹점 상세 정보 + await commerce.store.getStoreDetail(); + assert.strictEqual(requests[2].method.toLowerCase(), 'get'); + assert.strictEqual(requests[2].url, 'https://dev-api.bootapi.com/v1/store/detail'); + assert.ok(header(requests[2], 'Idempotency-Key'), 'Idempotency-Key header is required'); + + // 4) detail() 은 getStoreDetail() 의 alias + await commerce.store.detail('store-detail-key'); + assert.strictEqual(requests[3].url, 'https://dev-api.bootapi.com/v1/store/detail'); + assert.strictEqual(header(requests[3], 'Idempotency-Key'), 'store-detail-key'); + + console.log('commerce store: store / store/detail + Idempotency-Key'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/commerce/userMallSessionRequest.js b/test/commerce/userMallSessionRequest.js new file mode 100644 index 0000000..e41ac40 --- /dev/null +++ b/test/commerce/userMallSessionRequest.js @@ -0,0 +1,139 @@ +const assert = require('assert'); +const { BootpayCommerce } = require('../../dist/bootpay-commerce.js'); + +function header(config, name) { + if (!config.headers) return undefined; + if (typeof config.headers.get === 'function') return config.headers.get(name); + return config.headers[name] || config.headers[name.toLowerCase()] || config.headers[name.toUpperCase()]; +} + +function body(config) { + return typeof config.data === 'string' ? JSON.parse(config.data) : config.data; +} + +// Commerce API - 쇼핑몰(V1 Mall API) 회원 요청 규약 테스트 (네트워크 호출 없음) +// 1) 로그인: POST user/login (login_id / password / corporate_type) +// 2) 세션 조회: GET user/session (Bootpay-User-JWT) +// 3) 로그아웃: DELETE user/session (Bootpay-User-JWT) +// 4) 회원가입: POST user/join (null/undefined 값은 전송하지 않는다) +// 5) 중복 확인: GET user/join/{type}?pk={pk} +// * 위 endpoint 들은 users/... (레거시/외부 회원 API) 와 별개의 경로다. + +(async () => { + const commerce = new BootpayCommerce({ + client_key: 'ck', + secret_key: 'sk', + mode: 'development' + }); + + const requests = []; + commerce.$http.defaults.adapter = async (config) => { + requests.push(config); + return { + data: {}, + status: 200, + statusText: 'OK', + headers: {}, + config, + request: {} + }; + }; + + // 1) 회원 로그인 — corporate_type 미지정시 0 + await commerce.user.userLogin({ + login_id: 'test_user@example.com', + password: 'password123' + }); + assert.strictEqual(requests[0].method.toLowerCase(), 'post'); + assert.strictEqual(requests[0].url, 'https://dev-api.bootapi.com/v1/user/login'); + assert.deepStrictEqual(body(requests[0]), { + login_id: 'test_user@example.com', + password: 'password123', + corporate_type: 0 + }); + assert.ok(header(requests[0], 'Idempotency-Key'), 'Idempotency-Key header is required'); + + // 2) corporate_type 을 지정하면 그대로 전송한다 + await commerce.user.userLogin({ + login_id: 'biz@example.com', + password: 'password123', + corporate_type: 1, + idempotency_key: 'login-key' + }); + assert.deepStrictEqual(body(requests[1]), { + login_id: 'biz@example.com', + password: 'password123', + corporate_type: 1 + }); + assert.strictEqual(header(requests[1], 'Idempotency-Key'), 'login-key'); + + // 3) 회원 세션 조회 — Bootpay-User-JWT 헤더 + await commerce.user.userSession('USER_JWT'); + assert.strictEqual(requests[2].method.toLowerCase(), 'get'); + assert.strictEqual(requests[2].url, 'https://dev-api.bootapi.com/v1/user/session'); + assert.strictEqual(header(requests[2], 'Bootpay-User-JWT'), 'USER_JWT'); + + // 4) user_jwt 가 없으면 헤더를 붙이지 않는다 (Ruby SDK 의 headers.compact 와 동일 동작) + await commerce.user.userSession(); + assert.strictEqual(header(requests[3], 'Bootpay-User-JWT'), undefined); + assert.ok(header(requests[3], 'Idempotency-Key'), 'Idempotency-Key header is required'); + + // 5) 회원 로그아웃 — DELETE user/session + await commerce.user.userLogout('USER_JWT', 'logout-key'); + assert.strictEqual(requests[4].method.toLowerCase(), 'delete'); + assert.strictEqual(requests[4].url, 'https://dev-api.bootapi.com/v1/user/session'); + assert.strictEqual(header(requests[4], 'Bootpay-User-JWT'), 'USER_JWT'); + assert.strictEqual(header(requests[4], 'Idempotency-Key'), 'logout-key'); + + // 6) 회원가입 — 전달한 값만 전송, corporate_type 기본값 0 + await commerce.user.userJoin({ + login_id: 'test_user@example.com', + password: 'password123', + name: '테스트 사용자', + email: 'test_user@example.com', + phone: '010-1234-5678', + nickname: undefined, + gender: null + }); + assert.strictEqual(requests[5].method.toLowerCase(), 'post'); + assert.strictEqual(requests[5].url, 'https://dev-api.bootapi.com/v1/user/join'); + assert.deepStrictEqual(body(requests[5]), { + login_id: 'test_user@example.com', + password: 'password123', + name: '테스트 사용자', + email: 'test_user@example.com', + phone: '010-1234-5678', + corporate_type: 0 + }); + + // 7) 회원가입 중복 확인 — pk 는 query 로 전송 + await commerce.user.userJoinCheck('email-exist', 'test_user@example.com'); + assert.strictEqual(requests[6].method.toLowerCase(), 'get'); + assert.strictEqual( + requests[6].url, + 'https://dev-api.bootapi.com/v1/user/join/email-exist?pk=test_user%40example.com' + ); + assert.ok(header(requests[6], 'Idempotency-Key'), 'Idempotency-Key header is required'); + + await commerce.user.userJoinCheck('group-business-number-exist', '123-45-67890', 'check-key'); + assert.strictEqual( + requests[7].url, + 'https://dev-api.bootapi.com/v1/user/join/group-business-number-exist?pk=123-45-67890' + ); + assert.strictEqual(header(requests[7], 'Idempotency-Key'), 'check-key'); + + // 8) 레거시 회원 API (users/...) 는 그대로 유지된다 + await commerce.user.login('test_user@example.com', 'password123'); + assert.strictEqual(requests[8].url, 'https://dev-api.bootapi.com/v1/users/login'); + + await commerce.user.checkExist('email-exist', 'test_user@example.com'); + assert.strictEqual( + requests[9].url, + 'https://dev-api.bootapi.com/v1/users/join/email-exist?pk=test_user%40example.com' + ); + + console.log('commerce mall user: user/login, user/session, user/join endpoints + JWT header'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/test.md b/test/test.md index 312b131..49bf604 100644 --- a/test/test.md +++ b/test/test.md @@ -87,8 +87,19 @@ node test/commerce/mallSettingUpdate.js # charge_key / 몰 설정 요청 규약 검증 (네트워크 호출 없음, 키 불필요) node test/commerce/orderSubscriptionChargeRequest.js node test/commerce/mallSettingRequest.js + +# 쇼핑몰(V1 Mall API) 회원 세션 / 상품 / 가맹점 요청 규약 검증 (네트워크 호출 없음, 키 불필요) +node test/commerce/userMallSessionRequest.js +node test/commerce/productMallRequest.js +node test/commerce/storeRequest.js ``` +### 쇼핑몰(V1 Mall API) 회원 endpoint 주의 + +`user.userLogin / userSession / userLogout / userJoin / userJoinCheck` 는 단수형 `user/...` 경로를 사용하는 쇼핑몰 회원 API 다. +기존 `user.login / join / checkExist` 가 쓰는 복수형 `users/...` (외부 회원 연동 API) 와는 다른 endpoint 이므로 서로 대체할 수 없다. +세션이 필요한 호출에는 로그인시 받은 JWT 를 `Bootpay-User-JWT` 헤더로 전달한다. + ## 테스트 데이터 `test/config.js`에서 `TEST_DATA` 객체를 통해 테스트 데이터를 관리합니다: From 7eb289cf25b53450962c98f9f62686eb5f1522c3 Mon Sep 17 00:00:00 2001 From: Bootpay SDK Bot Date: Wed, 19 Aug 2026 01:42:34 +0000 Subject: [PATCH 117/117] =?UTF-8?q?sync:=203b22da9d=20*=20github-ruby=20?= =?UTF-8?q?=EC=A0=84=EC=9A=A9=EB=B6=84=20=EB=B0=98=EC=98=81=20(webhook=20?= =?UTF-8?q?=C2=B7=20image=5Fdestroy=20=C2=B7=20billing=5Fkey=20user=5Fid)?= =?UTF-8?q?=20*=20=EC=BB=A4=EB=A8=B8=EC=8A=A4=20API=2027=EC=A2=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20+=20=EC=A3=BD=EC=9D=80=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=205=EA=B1=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source: git.bootpay.co.kr/bootpay-sdk/backend-ruby@3b22da9d52c64cc943de9c5105bd411e9415c76f --- CHANGELOG.md | 30 ++ README.md | 31 +- package.json | 5 +- src/bootpay-commerce.ts | 3 + src/bootpay.ts | 5 +- src/lib/commerce-resource.ts | 32 +- src/lib/commerce/modules/index.ts | 1 + src/lib/commerce/modules/invoice.ts | 66 +++- src/lib/commerce/modules/order-cancel.ts | 101 ++++-- .../modules/order-subscription-adjustment.ts | 69 +++- .../modules/order-subscription-bill.ts | 38 ++- .../modules/order-subscription-request.ts | 87 +++-- .../commerce/modules/order-subscription.ts | 113 ++++++- src/lib/commerce/modules/order.ts | 4 + src/lib/commerce/modules/product.ts | 110 +++++-- src/lib/commerce/modules/user-group.ts | 41 ++- src/lib/commerce/modules/user.ts | 53 +++- src/lib/commerce/modules/webhook.ts | 33 ++ src/lib/commerce/types/index.ts | 1 + src/lib/commerce/types/invoice.ts | 23 +- src/lib/commerce/types/order-cancel.ts | 29 +- .../types/order-subscription-adjustment.ts | 9 +- .../commerce/types/order-subscription-bill.ts | 6 + .../types/order-subscription-request.ts | 23 ++ src/lib/commerce/types/order-subscription.ts | 62 ++++ src/lib/commerce/types/order.ts | 8 + src/lib/commerce/types/product.ts | 17 +- src/lib/commerce/types/user-group.ts | 15 +- src/lib/commerce/types/user.ts | 21 +- src/lib/commerce/types/webhook.ts | 9 + test/commerce/commerceRouteContract.js | 299 ++++++++++++++++++ test/commerce/orderCancelApprove.js | 6 +- test/commerce/orderCancelWithdraw.js | 8 +- test/commerce/orderSubscriptionPurchase.js | 29 ++ test/commerce/orderSubscriptionTransfer.js | 31 ++ test/commerce/userGroupLimit.js | 7 +- test/commerce/userMallSessionRequest.js | 43 ++- test/commerce/userUidExist.js | 28 ++ test/commerce/webhookSendTest.js | 29 ++ test/pg/lookupSequentialBillingKey.js | 4 +- test/pg/lookupSequentialBillingKeyRequest.js | 8 +- test/test.md | 29 +- 42 files changed, 1365 insertions(+), 201 deletions(-) create mode 100644 src/lib/commerce/modules/webhook.ts create mode 100644 src/lib/commerce/types/webhook.ts create mode 100644 test/commerce/commerceRouteContract.js create mode 100644 test/commerce/orderSubscriptionPurchase.js create mode 100644 test/commerce/orderSubscriptionTransfer.js create mode 100644 test/commerce/userUidExist.js create mode 100644 test/commerce/webhookSendTest.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 04f5964..c76b87a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +### 2.9.0 +* Commerce: 죽은 경로 정정 — 회원 endpoint 는 단수 `user/...` 가 아니라 복수 `users/...` 다 (commerce-api v1 에 단수 라우트가 없다) + - `user.userLogin`: `POST users/login` (v1/users/login#create) — `POST users/session` 은 라우트만 있고 create 액션이 없으므로 쓰지 않는다 + - `user.userSession`: `GET users/session` / `user.userLogout`: `DELETE users/session` + - `user.userJoin`: `POST users/join` / `user.userJoinCheck`: `GET users/join/{type}?pk={pk}` + - `userJoin`↔`join`, `userJoinCheck`↔`checkExist` 는 같은 endpoint 를 부르지만 서버가 파라미터 조합으로 분기하므로 둘 다 유지 +* Commerce: 신규 endpoint 추가 + - `user.uidExist(uid)`: `GET users/join/uid-exist?pk={uid}` — `*_exist` 전용형 5종 완성 + - `webhook.sendTest({ header_content_type })`: `POST webhook/test` — 테스트 웹훅 발송 + - `orderSubscription.requestIng.purchase`: `POST order_subscriptions/requests/ing/purchase` (중도인수 요청) + - `orderSubscription.requestIng.transfer`: `POST order_subscriptions/requests/ing/transfer` (이전/승계 요청) +* Commerce: multipart 전송 계층 신설 — `postMultipart` 추가 및 요청 인터셉터가 지정된 `Content-Type` 을 덮어쓰지 않도록 수정 + - 덮어쓰면 form-data 의 boundary 가 사라져 서버가 본문을 null 로 읽는 버그가 있었다 + - `product.create` 는 이미지가 없으면 JSON, 있으면 multipart(`images[0]`, `images[1]` … 인덱싱)로 전송 +* Commerce: 인자·응답 규약 정정 + - `invoice.list` 응답은 `{ items, total }` 이 아니라 `{ list, count }` — 타입 선언 정정, `limit` 기본값 24, `cs_type`/`user_id`/`product_type`/`css_at`/`cse_at` 파라미터 추가 + - `invoice.notify` 의 `sendTypes` 를 선택 인자로 변경 (미전달시 서버가 빈 배열로 처리) + - `orderCancel.approve`/`reject`/`withdraw` 인자명을 `order_cancellation_request_id` 로 통일 (구 이름 `order_cancel_request_history_id` 도 계속 지원) + - `orderSubscriptionAdjustment.delete` 는 대상 ID 를 query 가 아니라 body 로 전송 + - `orderSubscriptionAdjustment.update` 에 `adjustments` 배열 지원 (서버는 `duration` 회차 단위로 교체) + - `userGroup.limit` 에 `limit_month_purchase`/`limit_week_purchase` 추가 (서버 정식 인자명; `update` 로는 한도가 반영되지 않는다) + - `order.list` 에 `search_date_from`/`search_date_to` 추가 (`css_at`/`cse_at` 는 서버 별칭으로 계속 지원) + - `orderSubscription.list` 에 `search_date_from`/`search_date_to`/`status` 추가 + - `orderSubscriptionRequest.list` 에 `order_subscription_id`/`user_id`/`user_group_id` 추가 + - `product.products` 의 `keyword` 는 서버가 읽지 않음을 문서화 (인자는 하위호환 유지) +* Commerce: 서버가 요구하는 scope 를 endpoint 별로 명시 — 상품 쓰기/그룹 한도는 `manager`, 구독 계약변경·조정항목·요청 승인은 `supervisor`, 나머지는 `user` + - `orderSubscriptionRequest.list`/`detail` 은 `project_id` 가 있으면 `supervisor`, 없으면 `user` +* PG: `lookupSequentialBillingKey(widgetKey, billingKey, userId)` — `user_id` 쿼리 파라미터 추가 +* 의존성: `form-data` 를 dependencies 에 명시 (multipart 전송에 직접 사용) + ### 2.8.0 * Commerce: 쇼핑몰(V1 Mall API) 회원 endpoint 정정 및 추가 — 단수형 `user/...` 경로 사용 (기존 `users/...` 외부 회원 연동 API 는 그대로 유지) - `user.userLogin({ login_id, password, corporate_type })`: `POST user/login` — corporate_type 미지정시 0 diff --git a/README.md b/README.md index 2505c9f..9c08328 100644 --- a/README.md +++ b/README.md @@ -400,7 +400,7 @@ console.log(response) ``` ## 4-9. 우선순위 결제 빌링키 조회하기 -우선순위(순차) 결제에 사용되는 빌링키를 위젯키와 함께 조회합니다. +우선순위(순차) 결제에 사용되는 빌링키를 위젯키·회원 ID 와 함께 조회합니다. ```javascript (async () => { Bootpay.setConfiguration({ @@ -409,7 +409,7 @@ console.log(response) }) try { await Bootpay.getAccessToken() - const response = await Bootpay.lookupSequentialBillingKey('WIDGET_KEY', '66542dfb4d18d5fc7b43e1b6') + const response = await Bootpay.lookupSequentialBillingKey('WIDGET_KEY', '66542dfb4d18d5fc7b43e1b6', 'USER_ID') console.log(response) } catch (e) { console.log(e) @@ -642,8 +642,21 @@ await commerce.asSupervisor().orderSubscription.supervisorChargeRevoke({ ### 10-6. 청구서 관리 ```javascript -// 청구서 목록 조회 +// 청구서 목록 조회 — 응답은 { list, count } 구조이며 limit 기본값은 24 입니다. const invoices = await commerce.invoice.list() +const filtered = await commerce.invoice.list({ + page: 1, + limit: 24, + keyword: '청구서', + cs_type: 'CS_TYPE', + user_id: 'USER_ID', + product_type: 1, + css_at: '2024-01-01', + cse_at: '2024-12-31' +}) + +// 청구서 상세 조회 +const invoiceDetail = await commerce.invoice.detail('INVOICE_ID') // 청구서 생성 const invoice = await commerce.invoice.create({ @@ -652,10 +665,20 @@ const invoice = await commerce.invoice.create({ title: '청구서 제목' }) -// 청구서 알림 전송 +// 청구서 알림 재발송 — send_types 를 생략하면 서버가 빈 배열로 처리합니다. +// ⚠️ 실제 고객에게 알림이 발송되므로 테스트 호출에 주의하세요. await commerce.invoice.notify('INVOICE_ID', [1, 2]) // 1: SMS, 2: Email ``` +### 10-6-1. 테스트 웹훅 발송 + +등록된 웹훅 URL 로 테스트 페이로드를 보내 연동을 확인합니다. + +```javascript +await commerce.webhook.sendTest() +await commerce.webhook.sendTest({ header_content_type: 1 }) +``` + ### 10-7. 몰 설정 관리 supervisor scope 토큰(또는 키)으로만 호출할 수 있습니다. diff --git a/package.json b/package.json index d125f28..896a7b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bootpay/backend-js", - "version": "2.8.0", + "version": "2.9.0", "description": "Bootpay Server Side Package for Node.js", "types": "dist/bootpay.d.ts", "main": "dist/bootpay.js", @@ -18,7 +18,8 @@ "clear": "tsc --build --clean" }, "dependencies": { - "axios": "^1.7.2" + "axios": "^1.7.2", + "form-data": "^4.0.0" }, "devDependencies": { "@types/node": "^18.6.2", diff --git a/src/bootpay-commerce.ts b/src/bootpay-commerce.ts index 48c2478..ccc9e12 100644 --- a/src/bootpay-commerce.ts +++ b/src/bootpay-commerce.ts @@ -15,6 +15,7 @@ import { PointModule } from './lib/commerce/modules/point' import { CartModule } from './lib/commerce/modules/cart' import { StoreModule } from './lib/commerce/modules/store' import { MallSettingModule } from './lib/commerce/modules/mall-setting' +import { WebhookModule } from './lib/commerce/modules/webhook' export interface CommerceTokenResponse { access_token: string @@ -38,6 +39,7 @@ export class BootpayCommerce extends BootpayCommerceResource { public cart!: CartModule public store!: StoreModule public mallSetting!: MallSettingModule + public webhook!: WebhookModule constructor(configuration?: CommerceConfiguration) { super() @@ -64,6 +66,7 @@ export class BootpayCommerce extends BootpayCommerceResource { this.cart = new CartModule(this) this.store = new StoreModule(this) this.mallSetting = new MallSettingModule(this) + this.webhook = new WebhookModule(this) } /** diff --git a/src/bootpay.ts b/src/bootpay.ts index 217d0f0..432ab1e 100644 --- a/src/bootpay.ts +++ b/src/bootpay.ts @@ -165,11 +165,12 @@ class BootpayBackendNodejs extends BootpayBackendNodejsResource { * @date: 2026-07-03 * @param widgetKey: string * @param billingKey: string + * @param userId: string 조회 대상 회원 ID (서버가 빌링키 소유자 검증에 사용한다) * @returns Promise */ - async lookupSequentialBillingKey(widgetKey: string, billingKey: string): Promise { + async lookupSequentialBillingKey(widgetKey: string, billingKey: string, userId: string): Promise { try { - const response: SubscriptionBillingResponseParameters = await this.get(`subscribe/sequential_billing_key/${ billingKey }?widget_key=${ encodeURIComponent(widgetKey) }`) + const response: SubscriptionBillingResponseParameters = await this.get(`subscribe/sequential_billing_key/${ billingKey }?widget_key=${ encodeURIComponent(widgetKey) }&user_id=${ encodeURIComponent(userId) }`) return Promise.resolve(response) } catch (e) { return Promise.reject(e) diff --git a/src/lib/commerce-resource.ts b/src/lib/commerce-resource.ts index 32ae69d..b56017b 100644 --- a/src/lib/commerce-resource.ts +++ b/src/lib/commerce-resource.ts @@ -1,4 +1,5 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios' +import FormData from 'form-data' export interface BootpayCommerceRestApiErrorResponse { error_code?: number @@ -68,7 +69,11 @@ export class BootpayCommerceResource { this.$http.interceptors.request.use( (config: InternalAxiosRequestConfig) => { - config.headers.set('Content-Type', 'application/json') + // ⚠️ 요청이 Content-Type 을 직접 지정한 경우(multipart/form-data 등) 덮어쓰지 않는다. + // 덮어쓰면 form-data 가 붙인 boundary 가 사라져 본문이 서버에서 null 로 파싱된다. + if (!config.headers.has('Content-Type')) { + config.headers.set('Content-Type', 'application/json') + } config.headers.set('Accept', 'application/json') config.headers.set('Accept-Charset', 'utf-8') config.headers.set('BOOTPAY-SDK-VERSION', this.sdkVersion) @@ -172,6 +177,31 @@ export class BootpayCommerceResource { } } + /** + * multipart/form-data 전송 (파일 업로드용) + * ⚠️ Content-Type 은 form-data 가 생성한 값(boundary 포함)을 그대로 사용한다. + * 직접 지정하거나 인터셉터가 덮어쓰면 boundary 가 사라져 본문이 깨진다. + * 기존 post 는 JSON 고정이라 손대지 않고 별도 메서드로 둔다. + */ + async postMultipart( + url: string, + form: FormData, + config?: AxiosRequestConfig + ): Promise> { + try { + const response = await this.$http.post(this.entrypoints(url), form, { + ...config, + headers: { + ...config?.headers, + ...form.getHeaders() + } + }) + return Promise.resolve(response as unknown as BootpayCommerceResponse) + } catch (e) { + return Promise.reject(e) + } + } + async postWithBasicAuth( url: string, data?: D, diff --git a/src/lib/commerce/modules/index.ts b/src/lib/commerce/modules/index.ts index 8c13126..c7cab97 100644 --- a/src/lib/commerce/modules/index.ts +++ b/src/lib/commerce/modules/index.ts @@ -13,5 +13,6 @@ export * from './coupon' export * from './point' export * from './cart' export * from './mall-setting' +export * from './webhook' export * from './store' diff --git a/src/lib/commerce/modules/invoice.ts b/src/lib/commerce/modules/invoice.ts index ffffdfd..14e7238 100644 --- a/src/lib/commerce/modules/invoice.ts +++ b/src/lib/commerce/modules/invoice.ts @@ -1,6 +1,6 @@ import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' -import { CommerceInvoice, InvoiceListParams } from '../types' -import { ListParams } from '../types/common' +import { CommerceInvoice, InvoiceListParams, InvoiceListResponse } from '../types' +import { randomUUID } from 'crypto' export class InvoiceModule { private bootpay: BootpayCommerceResource @@ -11,17 +11,26 @@ export class InvoiceModule { /** * 청구서 목록 조회 + * GET /v1/invoices + * 응답은 { list: [...], count: N } 구조다 ({ items, total } 아님). + * limit 미지정시 서버 기본값과 동일한 24 를 보낸다. * @param params 조회 파라미터 */ - async list(params?: ListParams): Promise> { + async list(params?: InvoiceListParams): Promise> { + const { idempotency_key, ...rest } = params || {} const queryParams = new URLSearchParams() - if (params) { - if (params.page !== undefined) queryParams.append('page', params.page.toString()) - if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) - if (params.keyword) queryParams.append('keyword', params.keyword) - } - const query = queryParams.toString() - return this.bootpay.get<{ items: CommerceInvoice[]; total: number }>(`invoices${query ? `?${query}` : ''}`) + queryParams.append('page', (rest.page === undefined ? 1 : rest.page).toString()) + queryParams.append('limit', (rest.limit === undefined ? 24 : rest.limit).toString()) + if (rest.keyword) queryParams.append('keyword', rest.keyword) + if (rest.cs_type) queryParams.append('cs_type', rest.cs_type) + if (rest.user_id) queryParams.append('user_id', rest.user_id) + if (rest.product_type !== undefined) queryParams.append('product_type', rest.product_type.toString()) + if (rest.css_at) queryParams.append('css_at', rest.css_at) + if (rest.cse_at) queryParams.append('cse_at', rest.cse_at) + + return this.bootpay.get(`invoices?${queryParams.toString()}`, { + headers: this.invoiceHeaders(idempotency_key) + }) } /** @@ -33,19 +42,46 @@ export class InvoiceModule { } /** - * 청구서 알림 발송 + * 청구서 알림 재발송 + * POST /v1/invoices/{invoice_id}/notify + * sendTypes 미전달시 서버가 빈 배열로 처리한다. + * ⚠️ 실제 고객에게 알림이 발송되므로 테스트 호출 주의. * @param invoiceId 청구서 ID * @param sendTypes 발송 타입 배열 (예: [1, 2] - SMS, Email 등) + * @param idempotencyKey 미지정시 자동 생성 */ - async notify(invoiceId: string, sendTypes: number[]): Promise> { - return this.bootpay.post(`invoices/${invoiceId}/notify`, { send_types: sendTypes }) + async notify( + invoiceId: string, + sendTypes?: number[], + idempotencyKey?: string + ): Promise> { + const payload: Record = {} + if (sendTypes !== undefined && sendTypes !== null) payload.send_types = sendTypes + return this.bootpay.post(`invoices/${invoiceId}/notify`, payload, { + headers: this.invoiceHeaders(idempotencyKey) + }) } /** * 청구서 상세 조회 + * GET /v1/invoices/{invoice_id} * @param invoiceId 청구서 ID + * @param idempotencyKey 미지정시 자동 생성 */ - async detail(invoiceId: string): Promise> { - return this.bootpay.get(`invoices/${invoiceId}`) + async detail(invoiceId: string, idempotencyKey?: string): Promise> { + return this.bootpay.get(`invoices/${invoiceId}`, { + headers: this.invoiceHeaders(idempotencyKey) + }) + } + + /** + * 청구서 API 요청 헤더 + * Idempotency-Key 는 미지정시 매 호출마다 생성된다. + */ + private invoiceHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'user' + } } } diff --git a/src/lib/commerce/modules/order-cancel.ts b/src/lib/commerce/modules/order-cancel.ts index 0a7743a..e9618f3 100644 --- a/src/lib/commerce/modules/order-cancel.ts +++ b/src/lib/commerce/modules/order-cancel.ts @@ -3,8 +3,10 @@ import { OrderCancelListParams, OrderCancelParams, OrderCancelActionParams, + OrderCancelWithdrawParams, CommerceOrderCancelRequestHistory } from '../types' +import { randomUUID } from 'crypto' export class OrderCancelModule { private bootpay: BootpayCommerceResource @@ -14,17 +16,22 @@ export class OrderCancelModule { } /** - * 취소 요청 목록 조회 + * 주문 취소 요청 내역 조회 + * GET /v1/order/cancel + * order_number 또는 order_id 로 필터한다. 둘 다 없으면 전체. + * approve / reject / withdraw 에 넘길 order_cancellation_request_id 를 여기서 얻는다. * @param params 조회 파라미터 */ async list(params?: OrderCancelListParams): Promise> { + const { idempotency_key, ...rest } = params || {} const queryParams = new URLSearchParams() - if (params) { - if (params.order_id) queryParams.append('order_id', params.order_id) - if (params.order_number) queryParams.append('order_number', params.order_number) - } + if (rest.order_number) queryParams.append('order_number', rest.order_number) + if (rest.order_id) queryParams.append('order_id', rest.order_id) const query = queryParams.toString() - return this.bootpay.get<{ items: CommerceOrderCancelRequestHistory[]; total: number }>(`order/cancel${query ? `?${query}` : ''}`) + return this.bootpay.get<{ items: CommerceOrderCancelRequestHistory[]; total: number }>( + `order/cancel${query ? `?${query}` : ''}`, + { headers: this.userHeaders(idempotency_key) } + ) } /** @@ -36,38 +43,92 @@ export class OrderCancelModule { } /** - * 취소 요청 철회 - * @param orderCancelRequestHistoryId 취소 요청 이력 ID + * (구매자) 주문 취소 요청 철회 + * PUT /v1/order/cancel/{order_cancellation_request_id}/withdraw + * ⚠️ DELETE /v1/order/cancel/{id} 와는 다른 라우트다. 서버에 둘 다 있지만 매뉴얼이 문서화한 쪽은 withdraw 다. + * @param params 취소 요청 이력 ID (문자열로 바로 넘겨도 된다) */ - async withdraw(orderCancelRequestHistoryId: string): Promise> { - return this.bootpay.put(`order/cancel/${orderCancelRequestHistoryId}/withdraw`, {}) + async withdraw(params: OrderCancelWithdrawParams | string): Promise> { + const normalized = typeof params === 'string' ? { order_cancellation_request_id: params } : params + const cancellationId = this.cancellationId(normalized) + if (!cancellationId) { + return Promise.reject({ success: false, error: 'order_cancellation_request_id is required' }) + } + return this.bootpay.put(`order/cancel/${cancellationId}/withdraw`, {}, { + headers: this.userHeaders(normalized.idempotency_key) + }) } /** - * 취소 승인 + * (관리자) 취소 요청 승인 + * PUT /v1/order/cancel/{order_cancellation_request_id}/approve * @param params 취소 승인 파라미터 */ async approve(params: OrderCancelActionParams): Promise> { - if (!params.order_cancel_request_history_id) { - return Promise.reject({ success: false, error: 'order_cancel_request_history_id is required' }) + const cancellationId = this.cancellationId(params) + if (!cancellationId) { + return Promise.reject({ success: false, error: 'order_cancellation_request_id is required' }) } return this.bootpay.put( - `order/cancel/${params.order_cancel_request_history_id}/approve`, - params + `order/cancel/${cancellationId}/approve`, + this.actionPayload(params), + { headers: this.supervisorHeaders(params.idempotency_key) } ) } /** - * 취소 거절 + * (관리자) 취소 요청 반려 + * PUT /v1/order/cancel/{order_cancellation_request_id}/reject * @param params 취소 거절 파라미터 */ async reject(params: OrderCancelActionParams): Promise> { - if (!params.order_cancel_request_history_id) { - return Promise.reject({ success: false, error: 'order_cancel_request_history_id is required' }) + const cancellationId = this.cancellationId(params) + if (!cancellationId) { + return Promise.reject({ success: false, error: 'order_cancellation_request_id is required' }) } return this.bootpay.put( - `order/cancel/${params.order_cancel_request_history_id}/reject`, - params + `order/cancel/${cancellationId}/reject`, + this.actionPayload(params), + { headers: this.supervisorHeaders(params.idempotency_key) } + ) + } + + /** + * 취소 요청 이력 ID 를 뽑는다. + * 서버는 approve / reject / withdraw 셋 다 params[:id] 를 order_cancellation_request_id 로 동일하게 취급한다. + * 정식 이름은 order_cancellation_request_id 이며, 구 이름 order_cancel_request_history_id 도 계속 받는다. + */ + private cancellationId(params: OrderCancelActionParams | OrderCancelWithdrawParams): string | undefined { + return params.order_cancellation_request_id || params.order_cancel_request_history_id + } + + /** + * 승인/반려 payload — 서버가 읽는 값은 message 다. + */ + private actionPayload(params: OrderCancelActionParams): Record { + const { order_cancellation_request_id, order_cancel_request_history_id, idempotency_key, ...payload } = params + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null) ) } + + /** + * 구매자 scope 요청 헤더 + */ + private userHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'user' + } + } + + /** + * 관리자(승인/반려) scope 요청 헤더 + */ + private supervisorHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'supervisor' + } + } } diff --git a/src/lib/commerce/modules/order-subscription-adjustment.ts b/src/lib/commerce/modules/order-subscription-adjustment.ts index f7a558c..e3c31e1 100644 --- a/src/lib/commerce/modules/order-subscription-adjustment.ts +++ b/src/lib/commerce/modules/order-subscription-adjustment.ts @@ -1,6 +1,13 @@ import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' import { CommerceOrderSubscriptionAdjustment, OrderSubscriptionAdjustmentUpdateParams } from '../types' +import { randomUUID } from 'crypto' +/** + * 구독 가감산 조정항목 모듈 + * + * ⚠️ /adjustments 한 경로에 POST · PUT · DELETE 세 동사가 걸려 있다. + * 경로만 보고 메서드를 유추하지 말 것. + */ export class OrderSubscriptionAdjustmentModule { private bootpay: BootpayCommerceResource @@ -9,45 +16,85 @@ export class OrderSubscriptionAdjustmentModule { } /** - * 정기구독 조정 생성 + * 가감산 조정항목 추가 + * POST /v1/order_subscriptions/{order_subscription_id}/adjustments + * type 미전달시 서버가 price > 0 이면 SETUP_PRICE, 아니면 PERIOD_DISCOUNT 로 자동 판정한다. * @param orderSubscriptionId 정기구독 ID - * @param adjustment 조정 정보 + * @param adjustment 조정 정보 (price/duration/tax_free_price 미지정시 각각 0 / 1 / 0) + * @param idempotencyKey 미지정시 자동 생성 */ async create( orderSubscriptionId: string, - adjustment: CommerceOrderSubscriptionAdjustment + adjustment: CommerceOrderSubscriptionAdjustment, + idempotencyKey?: string ): Promise> { + const payload = this.compact({ + price: 0, + duration: 1, + tax_free_price: 0, + ...adjustment + }) return this.bootpay.post( `order_subscriptions/${orderSubscriptionId}/adjustments`, - adjustment + payload, + { headers: this.supervisorHeaders(idempotencyKey) } ) } /** - * 정기구독 조정 수정 + * 특정 회차의 조정항목을 통째로 교체 + * PUT /v1/order_subscriptions/{order_subscription_id}/adjustments + * 서버는 duration(회차) 단위로 adjustments 배열을 갈아끼운다. * @param params 수정 파라미터 */ async update(params: OrderSubscriptionAdjustmentUpdateParams): Promise> { if (!params.order_subscription_id) { return Promise.reject({ success: false, error: 'order_subscription_id is required' }) } + const { order_subscription_id, idempotency_key, ...payload } = params return this.bootpay.put( - `order_subscriptions/${params.order_subscription_id}/adjustments`, - params + `order_subscriptions/${order_subscription_id}/adjustments`, + this.compact({ duration: 1, ...payload }), + { headers: this.supervisorHeaders(idempotency_key) } ) } /** - * 정기구독 조정 삭제 + * 조정항목 삭제 + * DELETE /v1/order_subscriptions/{order_subscription_id}/adjustments + * ⚠️ 대상 ID 는 query 가 아니라 body 로 보낸다. * @param orderSubscriptionId 정기구독 ID * @param orderSubscriptionAdjustmentId 조정 ID + * @param idempotencyKey 미지정시 자동 생성 */ async delete( orderSubscriptionId: string, - orderSubscriptionAdjustmentId: string + orderSubscriptionAdjustmentId: string, + idempotencyKey?: string ): Promise> { - return this.bootpay.delete( - `order_subscriptions/${orderSubscriptionId}/adjustments?order_subscription_adjustment_id=${orderSubscriptionAdjustmentId}` + return this.bootpay.delete(`order_subscriptions/${orderSubscriptionId}/adjustments`, { + data: { order_subscription_adjustment_id: orderSubscriptionAdjustmentId }, + headers: this.supervisorHeaders(idempotencyKey) + }) + } + + /** + * null/undefined 값을 제거한다. (Ruby SDK 의 payload.compact 와 동일 동작) + */ + private compact(payload: Record): Record { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null) ) } + + /** + * 조정항목 API 요청 헤더 — 서버가 supervisor scope 를 요구한다. + * Idempotency-Key 는 미지정시 매 호출마다 생성된다. + */ + private supervisorHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'supervisor' + } + } } diff --git a/src/lib/commerce/modules/order-subscription-bill.ts b/src/lib/commerce/modules/order-subscription-bill.ts index 987b835..b7854f7 100644 --- a/src/lib/commerce/modules/order-subscription-bill.ts +++ b/src/lib/commerce/modules/order-subscription-bill.ts @@ -1,5 +1,6 @@ import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' import { CommerceOrderSubscriptionBill, OrderSubscriptionBillListParams } from '../types' +import { randomUUID } from 'crypto' export class OrderSubscriptionBillModule { private bootpay: BootpayCommerceResource @@ -9,22 +10,26 @@ export class OrderSubscriptionBillModule { } /** - * 정기구독 청구 목록 조회 + * 정기구독 빌(회차) 목록 조회 + * GET /v1/order_subscription_bills + * ⚠️ 경로가 order_subscription_bills — 언더스코어다 (하이픈 아님). + * page/limit 미지정시 각각 1 / 20 이 적용된다. * @param params 조회 파라미터 */ async list(params?: OrderSubscriptionBillListParams): Promise> { + const { idempotency_key, ...rest } = params || {} const queryParams = new URLSearchParams() - if (params) { - if (params.page !== undefined) queryParams.append('page', params.page.toString()) - if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) - if (params.keyword) queryParams.append('keyword', params.keyword) - if (params.order_subscription_id) queryParams.append('order_subscription_id', params.order_subscription_id) - if (params.status && params.status.length > 0) { - queryParams.append('status', params.status.join(',')) - } + if (rest.order_subscription_id) queryParams.append('order_subscription_id', rest.order_subscription_id) + queryParams.append('page', (rest.page === undefined ? 1 : rest.page).toString()) + queryParams.append('limit', (rest.limit === undefined ? 20 : rest.limit).toString()) + if (rest.keyword) queryParams.append('keyword', rest.keyword) + if (rest.status && rest.status.length > 0) { + queryParams.append('status', rest.status.join(',')) } - const query = queryParams.toString() - return this.bootpay.get<{ items: CommerceOrderSubscriptionBill[]; total: number }>(`order_subscription_bills${query ? `?${query}` : ''}`) + return this.bootpay.get<{ items: CommerceOrderSubscriptionBill[]; total: number }>( + `order_subscription_bills?${queryParams.toString()}`, + { headers: this.userHeaders(idempotency_key) } + ) } /** @@ -48,4 +53,15 @@ export class OrderSubscriptionBillModule { orderSubscriptionBill ) } + + /** + * 빌 조회 요청 헤더 + * Idempotency-Key 는 미지정시 매 호출마다 생성된다. + */ + private userHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'user' + } + } } diff --git a/src/lib/commerce/modules/order-subscription-request.ts b/src/lib/commerce/modules/order-subscription-request.ts index 7794b47..1b11b75 100644 --- a/src/lib/commerce/modules/order-subscription-request.ts +++ b/src/lib/commerce/modules/order-subscription-request.ts @@ -4,6 +4,7 @@ import { OrderSubscriptionRequestListParams, OrderSubscriptionRequestUpdateParams } from '../types' +import { randomUUID } from 'crypto' /** * V1 OrderSubscription Request 조회/승인 모듈 @@ -11,8 +12,11 @@ import { * 본인 모드 (user role): project_id 없이 호출 → 본인 요청 목록/단건 * 슈퍼바이저 모드 (supervisor role): project_id 포함 → 프로젝트 전체 + update (승인/거절) * - * 구매자측 요청 생성 (pause/resume/termination 등) 은 + * 구매자측 요청 생성 (pause/resume/purchase/termination/transfer) 은 * `commerce.orderSubscription.requestIng.*` 모듈을 사용한다. + * + * ⚠️ 경로가 order-subscription-requests — 하이픈이다. + * order_subscriptions · order_subscription_bills 는 언더스코어라 복사해 고칠 때 가장 흔히 틀리는 지점. */ export class OrderSubscriptionRequestModule { private bootpay: BootpayCommerceResource @@ -22,53 +26,96 @@ export class OrderSubscriptionRequestModule { } /** - * 요청 목록 조회 (user / supervisor 공용) + * 구독 변경요청 목록 조회 (user / supervisor 공용) + * GET /v1/order-subscription-requests + * project_id 를 주면 supervisor 모드(프로젝트 전체 검색), 없으면 본인 요청만 조회한다. + * page/limit 미지정시 각각 1 / 20 이 적용된다. */ async list( params?: OrderSubscriptionRequestListParams ): Promise> { + const { idempotency_key, ...rest } = params || {} const queryParams = new URLSearchParams() - if (params) { - if (params.project_id) queryParams.append('project_id', params.project_id) - if (params.page !== undefined) queryParams.append('page', params.page.toString()) - if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) - if (params.request_type !== undefined) queryParams.append('request_type', params.request_type.toString()) - if (params.status !== undefined) queryParams.append('status', params.status.toString()) - if (params.s_at) queryParams.append('s_at', params.s_at) - if (params.e_at) queryParams.append('e_at', params.e_at) - if (params.keyword) queryParams.append('keyword', params.keyword) - } - const query = queryParams.toString() + if (rest.project_id) queryParams.append('project_id', rest.project_id) + if (rest.order_subscription_id) queryParams.append('order_subscription_id', rest.order_subscription_id) + queryParams.append('page', (rest.page === undefined ? 1 : rest.page).toString()) + queryParams.append('limit', (rest.limit === undefined ? 20 : rest.limit).toString()) + if (rest.keyword) queryParams.append('keyword', rest.keyword) + if (rest.s_at) queryParams.append('s_at', rest.s_at) + if (rest.e_at) queryParams.append('e_at', rest.e_at) + if (rest.status !== undefined) queryParams.append('status', rest.status.toString()) + if (rest.request_type !== undefined) queryParams.append('request_type', rest.request_type.toString()) + if (rest.user_id) queryParams.append('user_id', rest.user_id) + if (rest.user_group_id) queryParams.append('user_group_id', rest.user_group_id) + return this.bootpay.get<{ items: OrderSubscriptionRequest[]; total: number }>( - `order-subscription-requests${query ? `?${query}` : ''}` + `order-subscription-requests?${queryParams.toString()}`, + { headers: this.requestHeaders(rest.project_id, idempotency_key) } ) } /** - * 요청 단건 조회 (user / supervisor 공용) + * 구독 변경요청 단건 조회 (user / supervisor 공용) + * GET /v1/order-subscription-requests/{id} */ async detail( orderSubscriptionRequestHistoryId: string, - projectId?: string + projectId?: string, + idempotencyKey?: string ): Promise> { const queryParams = new URLSearchParams() if (projectId) queryParams.append('project_id', projectId) const query = queryParams.toString() return this.bootpay.get( - `order-subscription-requests/${orderSubscriptionRequestHistoryId}${query ? `?${query}` : ''}` + `order-subscription-requests/${orderSubscriptionRequestHistoryId}${query ? `?${query}` : ''}`, + { headers: this.requestHeaders(projectId, idempotencyKey) } ) } /** - * 요청 승인/거절 (supervisor 전용) + * 구독 변경요청 승인/반려 (supervisor 전용) + * PUT /v1/order-subscription-requests/{id} + * ⚠️ 승인과 반려는 별도 액션이 아니다. 라우트는 index/show/update 셋뿐이고 + * approval: 'approve' | 'reject' 파라미터로 갈린다. + * 서버가 params[:action] 을 Rails 예약어로 쓰기 때문에 키 이름이 approval 이다. */ async update( params: OrderSubscriptionRequestUpdateParams ): Promise> { - const { order_subscription_request_history_id, ...rest } = params + const { order_subscription_request_history_id, idempotency_key, ...payload } = params return this.bootpay.put( `order-subscription-requests/${order_subscription_request_history_id}`, - rest + this.compact(payload as Record), + { headers: this.supervisorHeaders(idempotency_key) } + ) + } + + /** + * null/undefined 값을 제거한다. (Ruby SDK 의 payload.compact 와 동일 동작) + */ + private compact(payload: Record): Record { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null) ) } + + /** + * 조회 요청 헤더 — project_id 가 있으면 supervisor, 없으면 user scope 다. + */ + private requestHeaders(projectId?: string, idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': projectId ? 'supervisor' : 'user' + } + } + + /** + * 승인/반려 요청 헤더 — 서버가 supervisor scope 를 요구한다. + */ + private supervisorHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'supervisor' + } + } } diff --git a/src/lib/commerce/modules/order-subscription.ts b/src/lib/commerce/modules/order-subscription.ts index dc72805..b8196fe 100644 --- a/src/lib/commerce/modules/order-subscription.ts +++ b/src/lib/commerce/modules/order-subscription.ts @@ -5,6 +5,8 @@ import { OrderSubscriptionUpdateParams, OrderSubscriptionPauseParams, OrderSubscriptionResumeParams, + OrderSubscriptionPurchaseParams, + OrderSubscriptionTransferParams, OrderSubscriptionTerminationParams, CalcTerminateFeeResponse, SupervisorOrderSubscriptionApproveParams, @@ -27,29 +29,74 @@ export class OrderSubscriptionRequestIngModule { } /** - * 정기구독 일시정지 + * 정기구독 일시정지 요청 + * POST /v1/order_subscriptions/requests/ing/pause * @param params 일시정지 파라미터 */ async pause(params: OrderSubscriptionPauseParams): Promise> { - return this.bootpay.post('order_subscriptions/requests/ing/pause', params) + const { idempotency_key, ...payload } = params + return this.bootpay.post( + 'order_subscriptions/requests/ing/pause', + this.compact(payload), + { headers: this.userHeaders(idempotency_key) } + ) } /** - * 정기구독 재개 + * 정기구독 재개 요청 + * PUT /v1/order_subscriptions/requests/ing/resume + * ⚠️ requests/ing 계열 중 유일하게 PUT 이다. 오타로 보고 POST 로 바꾸지 말 것. * @param params 재개 파라미터 */ async resume(params: OrderSubscriptionResumeParams): Promise> { - return this.bootpay.put('order_subscriptions/requests/ing/resume', params) + const { idempotency_key, ...payload } = params + return this.bootpay.put( + 'order_subscriptions/requests/ing/resume', + this.compact(payload), + { headers: this.userHeaders(idempotency_key) } + ) + } + + /** + * 중도인수 요청 + * POST /v1/order_subscriptions/requests/ing/purchase + * @param params 중도인수 파라미터 + */ + async purchase(params: OrderSubscriptionPurchaseParams): Promise> { + const { idempotency_key, ...payload } = params + return this.bootpay.post( + 'order_subscriptions/requests/ing/purchase', + this.compact(payload), + { headers: this.userHeaders(idempotency_key) } + ) } /** - * 해지 수수료 계산 + * 구독 이전/승계 요청 + * POST /v1/order_subscriptions/requests/ing/transfer + * @param params 이전/승계 파라미터 + */ + async transfer(params: OrderSubscriptionTransferParams): Promise> { + const { idempotency_key, ...payload } = params + return this.bootpay.post( + 'order_subscriptions/requests/ing/transfer', + this.compact(payload), + { headers: this.userHeaders(idempotency_key) } + ) + } + + /** + * 중도해지 수수료 사전계산 + * GET /v1/order_subscriptions/requests/ing/calculate_termination_fee + * 해지 요청 전에 얼마가 나오는지 미리 보여줄 때 쓴다. * @param orderSubscriptionId 정기구독 ID (선택) * @param orderNumber 주문번호 (선택) + * @param idempotencyKey 미지정시 자동 생성 */ async calculateTerminationFee( orderSubscriptionId?: string, - orderNumber?: string + orderNumber?: string, + idempotencyKey?: string ): Promise> { if (!orderSubscriptionId && !orderNumber) { return Promise.reject({ @@ -59,14 +106,12 @@ export class OrderSubscriptionRequestIngModule { } const queryParams = new URLSearchParams() - if (orderSubscriptionId) { - queryParams.append('order_subscription_id', orderSubscriptionId) - } else if (orderNumber) { - queryParams.append('order_number', orderNumber) - } + if (orderSubscriptionId) queryParams.append('order_subscription_id', orderSubscriptionId) + if (orderNumber) queryParams.append('order_number', orderNumber) return this.bootpay.get( - `order_subscriptions/requests/ing/calculate_termination_fee?${queryParams.toString()}` + `order_subscriptions/requests/ing/calculate_termination_fee?${queryParams.toString()}`, + { headers: this.userHeaders(idempotencyKey) } ) } @@ -81,11 +126,37 @@ export class OrderSubscriptionRequestIngModule { } /** - * 정기구독 해지 + * 중도해지 요청 + * POST /v1/order_subscriptions/requests/ing/termination * @param params 해지 파라미터 */ async termination(params: OrderSubscriptionTerminationParams): Promise> { - return this.bootpay.post('order_subscriptions/requests/ing/termination', params) + const { idempotency_key, ...payload } = params + return this.bootpay.post( + 'order_subscriptions/requests/ing/termination', + this.compact(payload), + { headers: this.userHeaders(idempotency_key) } + ) + } + + /** + * null/undefined 값을 제거한다. (Ruby SDK 의 payload.compact 와 동일 동작) + */ + private compact(payload: Record): Record { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null) + ) + } + + /** + * requests/ing 요청 헤더 — 구매자가 올리는 요청이므로 user scope 다. + * Idempotency-Key 는 미지정시 매 호출마다 생성된다. + */ + private userHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'user' + } } } @@ -108,10 +179,13 @@ export class OrderSubscriptionModule { if (params.page !== undefined) queryParams.append('page', params.page.toString()) if (params.limit !== undefined) queryParams.append('limit', params.limit.toString()) if (params.keyword) queryParams.append('keyword', params.keyword) + if (params.search_date_from) queryParams.append('search_date_from', params.search_date_from) + if (params.search_date_to) queryParams.append('search_date_to', params.search_date_to) if (params.s_at) queryParams.append('s_at', params.s_at) if (params.e_at) queryParams.append('e_at', params.e_at) if (params.request_type) queryParams.append('request_type', params.request_type) if (params.user_group_id) queryParams.append('user_group_id', params.user_group_id) + if (params.status !== undefined) queryParams.append('status', params.status.toString()) if (params.user_id) queryParams.append('user_id', params.user_id) } const query = queryParams.toString() @@ -127,14 +201,21 @@ export class OrderSubscriptionModule { } /** - * 정기구독 수정 + * 구독 계약 내용 변경 + * PUT /v1/order_subscriptions/{order_subscription_id} + * 바뀐 값만 보내면 된다 (나머지는 서버가 그대로 유지한다). * @param params 수정 파라미터 */ async update(params: OrderSubscriptionUpdateParams): Promise> { if (!params.order_subscription_id) { return Promise.reject({ success: false, error: 'order_subscription_id is required' }) } - return this.bootpay.put(`order_subscriptions/${params.order_subscription_id}`, params) + const { order_subscription_id, idempotency_key, ...payload } = params + return this.bootpay.put( + `order_subscriptions/${order_subscription_id}`, + this.compact(payload), + { headers: this.supervisorHeaders(idempotency_key) } + ) } async supervisorApprove( diff --git a/src/lib/commerce/modules/order.ts b/src/lib/commerce/modules/order.ts index ac12243..966685e 100644 --- a/src/lib/commerce/modules/order.ts +++ b/src/lib/commerce/modules/order.ts @@ -10,6 +10,8 @@ export class OrderModule { /** * 주문 목록 조회 + * GET /v1/orders + * limit 은 서버 기본 20 · 최대 50 (초과분은 서버가 50 으로 클램프한다). * @param params 조회 파라미터 */ async list(params?: OrderListParams): Promise> { @@ -21,6 +23,8 @@ export class OrderModule { if (params.user_id) queryParams.append('user_id', params.user_id) if (params.user_group_id) queryParams.append('user_group_id', params.user_group_id) if (params.cs_type) queryParams.append('cs_type', params.cs_type) + if (params.search_date_from) queryParams.append('search_date_from', params.search_date_from) + if (params.search_date_to) queryParams.append('search_date_to', params.search_date_to) if (params.css_at) queryParams.append('css_at', params.css_at) if (params.cse_at) queryParams.append('cse_at', params.cse_at) if (params.subscription_billing_type !== undefined) { diff --git a/src/lib/commerce/modules/product.ts b/src/lib/commerce/modules/product.ts index b89ef09..dd021a9 100644 --- a/src/lib/commerce/modules/product.ts +++ b/src/lib/commerce/modules/product.ts @@ -37,6 +37,8 @@ export class ProductModule { * 상품 목록 조회 (V1 Mall API) * GET /v1/products * page/limit 은 미지정시 각각 1 / 20 이 적용되고, 나머지 값은 지정된 것만 전송한다. + * ⚠️ keyword 는 서버(v1/products_controller#index)가 읽지 않는다 — page/limit/category_id/ex_uid/sort 만 사용하며 + * keyword 를 보내도 조용히 무시된다. 하위호환 때문에 인자는 남겨두되, 검색이 필요하면 서버 지원이 선행되어야 한다. * @param params 조회 파라미터 */ async products(params?: MallProductListParams): Promise> { @@ -59,42 +61,36 @@ export class ProductModule { } /** - * 상품 생성 (이미지 포함) - * @param product 상품 정보 + * 상품 생성 + * POST /v1/products + * imagePaths 가 있으면 multipart/form-data, 없으면 JSON 으로 보낸다. + * @param product 상품 정보 (여기 명시되지 않은 값도 서버 _product_params 로 그대로 전달된다) * @param imagePaths 이미지 파일 경로 배열 + * @param idempotencyKey 미지정시 자동 생성 */ - async create(product: CommerceProduct, imagePaths?: string[]): Promise> { - const formData = new FormData() - - // 상품 정보를 JSON으로 변환하여 추가 - Object.entries(product).forEach(([key, value]) => { - if (value !== undefined && value !== null) { - if (typeof value === 'object') { - formData.append(key, JSON.stringify(value)) - } else { - formData.append(key, String(value)) - } - } - }) + async create( + product: CommerceProduct, + imagePaths?: string[], + idempotencyKey?: string + ): Promise> { + const payload = this.compact(product as Record) + const headers = this.managerHeaders(idempotencyKey) - // 이미지 파일 추가 - if (imagePaths && imagePaths.length > 0) { - for (const imagePath of imagePaths) { - const fileName = path.basename(imagePath) - formData.append('images', fs.createReadStream(imagePath), fileName) - } + if (!imagePaths || imagePaths.length === 0) { + return this.bootpay.post('products', payload, { headers }) } - const mode = this.bootpay.commerceConfiguration.mode || 'production' - const url = `${this.bootpay.API_ENTRYPOINTS[mode]}/products` + const formData = new FormData() + Object.entries(payload).forEach(([key, value]) => { + formData.append(key, this.multipartValue(value)) + }) - return this.bootpay.$http.post(url, formData, { - headers: { - ...formData.getHeaders(), - Authorization: this.bootpay.authorizationHeader(), - 'BOOTPAY-ROLE': this.bootpay.getRole() || 'user' - } + // ⚠️ Rails 는 반복된 `images` 를 배열로 받지 않는다. images[0], images[1] ... 로 인덱싱해야 한다. + imagePaths.forEach((imagePath, index) => { + formData.append(`images[${index}]`, fs.createReadStream(imagePath), path.basename(imagePath)) }) + + return this.bootpay.postMultipart('products', formData, { headers }) } /** @@ -124,32 +120,76 @@ export class ProductModule { /** * 상품 수정 + * PUT /v1/products/{product_id} + * 바뀐 값만 보내면 된다. ⚠️ category_id 는 키 존재 여부로 '해제 의사'를 판별하므로 주의. * @param product 상품 정보 + * @param idempotencyKey 미지정시 자동 생성 */ - async update(product: CommerceProduct): Promise> { + async update(product: CommerceProduct, idempotencyKey?: string): Promise> { if (!product.product_id) { return Promise.reject({ success: false, error: 'product_id is required' }) } - return this.bootpay.put(`products/${product.product_id}`, product) + return this.bootpay.put( + `products/${product.product_id}`, + this.compact(product as Record), + { headers: this.managerHeaders(idempotencyKey) } + ) } /** - * 상품 상태 변경 + * 상품 판매/노출 상태 변경 + * PUT /v1/products/{product_id}/status + * ⚠️ 재고(stock)는 여기가 아니라 update 로 바꾼다. * @param params 상태 변경 파라미터 */ async status(params: ProductStatusParams): Promise> { if (!params.product_id) { return Promise.reject({ success: false, error: 'product_id is required' }) } - return this.bootpay.put(`products/${params.product_id}/status`, params) + const { product_id, idempotency_key, ...payload } = params + return this.bootpay.put(`products/${product_id}/status`, this.compact(payload), { + headers: this.managerHeaders(idempotency_key) + }) } /** * 상품 삭제 + * DELETE /v1/products/{product_id} * @param productId 상품 ID + * @param idempotencyKey 미지정시 자동 생성 + */ + async delete(productId: string, idempotencyKey?: string): Promise> { + return this.bootpay.delete(`products/${productId}`, { + headers: this.managerHeaders(idempotencyKey) + }) + } + + /** + * 상품 쓰기(등록/수정/삭제/상태변경) 요청 헤더 + * 서버가 manager scope 를 요구한다. + */ + private managerHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'manager' + } + } + + /** + * null/undefined 값을 제거한다. (Ruby SDK 의 payload.compact 와 동일 동작) + */ + private compact(payload: Record): Record { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null) + ) + } + + /** + * multipart form 값 정규화 — 배열/객체는 JSON, 나머지는 문자열로 보낸다. */ - async delete(productId: string): Promise> { - return this.bootpay.delete(`products/${productId}`) + private multipartValue(value: any): string { + if (typeof value === 'object') return JSON.stringify(value) + return String(value) } /** diff --git a/src/lib/commerce/modules/user-group.ts b/src/lib/commerce/modules/user-group.ts index 79e7e27..2d7dd2d 100644 --- a/src/lib/commerce/modules/user-group.ts +++ b/src/lib/commerce/modules/user-group.ts @@ -1,5 +1,6 @@ import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' import { CommerceUserGroup, UserGroupListParams, UserGroupLimitParams, UserGroupAggregateTransactionParams } from '../types' +import { randomUUID } from 'crypto' export class UserGroupModule { private bootpay: BootpayCommerceResource @@ -70,24 +71,56 @@ export class UserGroupModule { } /** - * 그룹 제한 설정 + * 그룹 구매한도 설정 + * PUT /v1/user-groups/{user_group_id}/limit + * ⚠️ update 로는 한도가 절대 반영되지 않는다 — 서버 user_groups_controller#update 가 + * use_limit / limit_message / limit_month_purchase / limit_week_purchase 를 명시적으로 제거하기 때문이다. + * 한도는 이 전용 라우트로만 바뀐다. 서버 scope: manager:limit * @param params 제한 설정 파라미터 */ async limit(params: UserGroupLimitParams): Promise> { if (!params.user_group_id) { return Promise.reject({ success: false, error: 'user_group_id is required' }) } - return this.bootpay.put(`user-groups/${params.user_group_id}/limit`, params) + const { user_group_id, idempotency_key, ...payload } = params + return this.bootpay.put(`user-groups/${user_group_id}/limit`, this.compact(payload), { + headers: this.managerHeaders(idempotency_key) + }) } /** - * 그룹 거래 집계 조회 + * 그룹 구독 합산청구(정산주기) 설정 변경 + * PUT /v1/user-groups/{user_group_id}/aggregate-transaction + * update 에도 같은 이름의 인자가 있지만 서버는 이 전용 라우트에서만 처리한다. * @param params 집계 파라미터 */ async aggregateTransaction(params: UserGroupAggregateTransactionParams): Promise> { if (!params.user_group_id) { return Promise.reject({ success: false, error: 'user_group_id is required' }) } - return this.bootpay.put(`user-groups/${params.user_group_id}/aggregate-transaction`, params) + const { user_group_id, idempotency_key, ...payload } = params + return this.bootpay.put(`user-groups/${user_group_id}/aggregate-transaction`, this.compact(payload), { + headers: this.managerHeaders(idempotency_key) + }) + } + + /** + * null/undefined 값을 제거한다. (Ruby SDK 의 payload.compact 와 동일 동작) + */ + private compact(payload: Record): Record { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null) + ) + } + + /** + * 그룹 한도/합산청구 설정 요청 헤더 — 서버가 manager scope 를 요구한다. + * Idempotency-Key 는 미지정시 매 호출마다 생성된다. + */ + private managerHeaders(idempotencyKey?: string): Record { + return { + 'Idempotency-Key': idempotencyKey || randomUUID(), + 'BOOTPAY-ROLE': 'manager' + } } } diff --git a/src/lib/commerce/modules/user.ts b/src/lib/commerce/modules/user.ts index 2efebe4..e7af602 100644 --- a/src/lib/commerce/modules/user.ts +++ b/src/lib/commerce/modules/user.ts @@ -65,22 +65,25 @@ export class UserModule { } /** - * 회원 로그인 (V1 Mall API) - * POST /v1/user/login + * 회원 로그인 (V1 API) + * POST /v1/users/login + * v1 에는 단수 user/* 라우트가 없다. 로그인은 v1/users/login#create 다. + * ⚠️ POST /v1/users/session 은 resource :session 이 만들어낸 라우트일 뿐 create 액션이 없다 — 그리로 보내면 안 된다. + * ⚠️ 서버(LoginService)는 login_id/password 만 읽는다. corporate_type 은 전달돼도 무시된다. * @param params 로그인 파라미터 (corporate_type 미지정시 0) */ async userLogin(params: MallUserLoginParams): Promise> { const { idempotency_key, corporate_type, ...rest } = params return this.bootpay.post( - 'user/login', + 'users/login', this.compact({ ...rest, corporate_type: corporate_type === undefined ? 0 : corporate_type }), { headers: this.mallHeaders(undefined, idempotency_key) } ) } /** - * 회원 세션 조회 (V1 Mall API) - * GET /v1/user/session + * 회원 세션 조회 (V1 API) + * GET /v1/users/session * @param userJwt 로그인시 발급받은 회원 JWT * @param idempotencyKey 미지정시 자동 생성 */ @@ -88,41 +91,46 @@ export class UserModule { userJwt?: string, idempotencyKey?: string ): Promise> { - return this.bootpay.get('user/session', { + return this.bootpay.get('users/session', { headers: this.mallHeaders(userJwt, idempotencyKey) }) } /** - * 회원 로그아웃 (V1 Mall API) - * DELETE /v1/user/session + * 회원 로그아웃 (V1 API) + * DELETE /v1/users/session * @param userJwt 로그인시 발급받은 회원 JWT * @param idempotencyKey 미지정시 자동 생성 */ async userLogout(userJwt: string, idempotencyKey?: string): Promise> { - return this.bootpay.delete('user/session', { + return this.bootpay.delete('users/session', { headers: this.mallHeaders(userJwt, idempotencyKey) }) } /** - * 회원가입 (V1 Mall API) - * POST /v1/user/join + * 회원가입 (V1 API) — 일반 회원가입용 + * POST /v1/users/join + * ⚠️ join(user) 과 같은 엔드포인트를 부른다. 중복이 아니라 용도가 다르다 — + * 이쪽은 password/corporate_type/group 을 쓰는 일반 회원가입, 저쪽은 uid/login_email/login_pw 를 쓰는 외부 uid 연동 가입이다. + * 서버가 파라미터 조합으로 분기하므로 둘 다 유지한다. * @param params 회원가입 파라미터 (corporate_type 미지정시 0, 나머지 null/undefined 값은 전송하지 않는다) */ async userJoin(params: MallUserJoinParams): Promise> { const { idempotency_key, corporate_type, ...rest } = params return this.bootpay.post( - 'user/join', + 'users/join', this.compact({ ...rest, corporate_type: corporate_type === undefined ? 0 : corporate_type }), { headers: this.mallHeaders(undefined, idempotency_key) } ) } /** - * 회원가입 중복 확인 (V1 Mall API) - * GET /v1/user/join/{type}?pk={pk} - * @param type email-exist, id-exist, phone-exist, group-business-number-exist + * 회원가입 중복 확인 (V1 API) — key 를 인자로 받는 일반형 + * GET /v1/users/join/{type}?pk={pk} + * ⚠️ uidExist 등 전용형과 기능이 겹치지만 둘 다 유지한다. + * 일반형은 서버에 새 key 가 생겨도 SDK 수정 없이 쓸 수 있다. + * @param type email-exist, id-exist, phone-exist, uid-exist, group-business-number-exist * @param pk 중복 확인할 값 * @param idempotencyKey 미지정시 자동 생성 */ @@ -131,11 +139,24 @@ export class UserModule { pk: string, idempotencyKey?: string ): Promise> { - return this.bootpay.get<{ exists: boolean }>(`user/join/${type}?pk=${encodeURIComponent(pk)}`, { + return this.bootpay.get<{ exists: boolean }>(`users/join/${type}?pk=${encodeURIComponent(pk)}`, { headers: this.mallHeaders(undefined, idempotencyKey) }) } + /** + * 외부 uid(ex_uid) 중복 검사 + * GET /v1/users/join/uid-exist?pk={uid} + * email-exist / id-exist / phone-exist / group-business-number-exist 와 같은 전용형이다. + * @param uid 중복 확인할 외부 uid + * @param idempotencyKey 미지정시 자동 생성 + */ + async uidExist(uid: string, idempotencyKey?: string): Promise> { + return this.bootpay.get<{ exists: boolean }>(`users/join/uid-exist?pk=${encodeURIComponent(uid)}`, { + headers: { ...this.mallHeaders(undefined, idempotencyKey), 'BOOTPAY-ROLE': 'user' } + }) + } + /** * 사용자 목록 조회 * @param params 조회 파라미터 diff --git a/src/lib/commerce/modules/webhook.ts b/src/lib/commerce/modules/webhook.ts new file mode 100644 index 0000000..caf2fa8 --- /dev/null +++ b/src/lib/commerce/modules/webhook.ts @@ -0,0 +1,33 @@ +import { BootpayCommerceResource, BootpayCommerceResponse } from '../../commerce-resource' +import { SendTestWebhookParams } from '../types' +import { randomUUID } from 'crypto' + +export class WebhookModule { + private bootpay: BootpayCommerceResource + + constructor(bootpay: BootpayCommerceResource) { + this.bootpay = bootpay + } + + /** + * 테스트 웹훅 발송 + * POST /v1/webhook/test + * 등록된 웹훅 URL 로 테스트 페이로드를 보내 연동을 확인할 때 쓴다. + * @param params 발송 파라미터 (header_content_type 미지정시 전송하지 않는다) + */ + async sendTest(params?: SendTestWebhookParams): Promise> { + const { idempotency_key, ...payload } = params || {} + return this.bootpay.post('webhook/test', this.compact(payload), { + headers: { 'Idempotency-Key': idempotency_key || randomUUID() } + }) + } + + /** + * null/undefined 값을 제거한다. (Ruby SDK 의 payload.compact 와 동일 동작) + */ + private compact(payload: Record): Record { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null) + ) + } +} diff --git a/src/lib/commerce/types/index.ts b/src/lib/commerce/types/index.ts index 68dab5a..c61c0e9 100644 --- a/src/lib/commerce/types/index.ts +++ b/src/lib/commerce/types/index.ts @@ -14,3 +14,4 @@ export * from './coupon' export * from './point' export * from './cart' export * from './mall-setting' +export * from './webhook' diff --git a/src/lib/commerce/types/invoice.ts b/src/lib/commerce/types/invoice.ts index 3134a74..851edff 100644 --- a/src/lib/commerce/types/invoice.ts +++ b/src/lib/commerce/types/invoice.ts @@ -92,7 +92,28 @@ export interface CommerceInvoiceItem { tax_free_price?: number } -export interface InvoiceListParams extends ListParams {} +/** + * 청구서 목록 조회 파라미터 (GET /v1/invoices) + * limit 미지정시 서버 기본값과 동일한 24 로 전송된다. + */ +export interface InvoiceListParams extends ListParams { + cs_type?: string + user_id?: string + product_type?: number + css_at?: string + cse_at?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, query 에는 포함되지 않는다) */ + idempotency_key?: string +} + +/** + * 청구서 목록 조회 응답 (GET /v1/invoices) + * ⚠️ { items, total } 이 아니라 { list, count } 다. + */ +export interface InvoiceListResponse { + list?: CommerceInvoice[] + count?: number +} export interface InvoiceCreateParams { user_id?: string diff --git a/src/lib/commerce/types/order-cancel.ts b/src/lib/commerce/types/order-cancel.ts index e55ef19..5b07809 100644 --- a/src/lib/commerce/types/order-cancel.ts +++ b/src/lib/commerce/types/order-cancel.ts @@ -1,6 +1,12 @@ +/** + * 주문 취소 요청 내역 조회 파라미터 (GET /v1/order/cancel) + * 둘 다 없으면 전체를 조회한다. + */ export interface OrderCancelListParams { order_id?: string order_number?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, query 에는 포함되지 않는다) */ + idempotency_key?: string } export interface CancelProduct { @@ -29,10 +35,31 @@ export interface OrderCancelParams { is_supervisor?: boolean } +/** + * 취소 요청 승인/반려 파라미터 (PUT /v1/order/cancel/{id}/approve · /reject) + * 서버는 approve / reject / withdraw 셋 다 params[:id] 를 order_cancellation_request_id 로 동일하게 취급한다. + * 정식 이름은 order_cancellation_request_id 이며, 구 이름 order_cancel_request_history_id 도 계속 받는다. + */ export interface OrderCancelActionParams { - order_cancel_request_history_id: string + order_cancellation_request_id?: string + /** @deprecated order_cancellation_request_id 를 사용할 것 (하위호환으로 계속 지원한다) */ + order_cancel_request_history_id?: string + message?: string cancel_reason?: string refund_price?: number + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string +} + +/** + * 취소 요청 철회 파라미터 (PUT /v1/order/cancel/{id}/withdraw) + */ +export interface OrderCancelWithdrawParams { + order_cancellation_request_id?: string + /** @deprecated order_cancellation_request_id 를 사용할 것 (하위호환으로 계속 지원한다) */ + order_cancel_request_history_id?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송) */ + idempotency_key?: string } export interface CommerceOrderCancelRequestHistory { diff --git a/src/lib/commerce/types/order-subscription-adjustment.ts b/src/lib/commerce/types/order-subscription-adjustment.ts index 3baf678..9a74d12 100644 --- a/src/lib/commerce/types/order-subscription-adjustment.ts +++ b/src/lib/commerce/types/order-subscription-adjustment.ts @@ -11,12 +11,19 @@ export interface CommerceOrderSubscriptionAdjustment { created_at?: string } +/** + * 조정항목 수정 파라미터 (PUT /v1/order_subscriptions/{order_subscription_id}/adjustments) + * 서버는 duration(회차) 단위로 adjustments 배열을 통째로 교체한다. duration 미지정시 1 이 적용된다. + */ export interface OrderSubscriptionAdjustmentUpdateParams { order_subscription_id: string - order_subscription_adjustment_id?: string duration?: number + adjustments?: CommerceOrderSubscriptionAdjustment[] + order_subscription_adjustment_id?: string price?: number tax_free_price?: number name?: string type?: number + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string } diff --git a/src/lib/commerce/types/order-subscription-bill.ts b/src/lib/commerce/types/order-subscription-bill.ts index 06f6e9b..ff9447a 100644 --- a/src/lib/commerce/types/order-subscription-bill.ts +++ b/src/lib/commerce/types/order-subscription-bill.ts @@ -62,7 +62,13 @@ export interface CommerceOrderSubscriptionBill { service_end_at?: string } +/** + * 구독 빌(회차) 목록 조회 파라미터 (GET /v1/order_subscription_bills) + * page/limit 미지정시 각각 1 / 20 이 적용된다. + */ export interface OrderSubscriptionBillListParams extends ListParams { order_subscription_id?: string status?: number[] + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, query 에는 포함되지 않는다) */ + idempotency_key?: string } diff --git a/src/lib/commerce/types/order-subscription-request.ts b/src/lib/commerce/types/order-subscription-request.ts index c39dde4..73aadac 100644 --- a/src/lib/commerce/types/order-subscription-request.ts +++ b/src/lib/commerce/types/order-subscription-request.ts @@ -12,8 +12,14 @@ export interface OrderSubscriptionRequest { updated_at?: string } +/** + * 구독 변경요청 목록 조회 파라미터 (GET /v1/order-subscription-requests) + * project_id 를 주면 supervisor 모드(프로젝트 전체 검색), 없으면 본인 요청만 조회한다. + * page/limit 미지정시 각각 1 / 20 이 적용된다. + */ export interface OrderSubscriptionRequestListParams { project_id?: string + order_subscription_id?: string page?: number limit?: number request_type?: number @@ -21,13 +27,30 @@ export interface OrderSubscriptionRequestListParams { s_at?: string e_at?: string keyword?: string + user_id?: string + user_group_id?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, query 에는 포함되지 않는다) */ + idempotency_key?: string } export type OrderSubscriptionRequestApprovalAction = 'approve' | 'reject' +/** + * 구독 변경요청 승인/반려 파라미터 (PUT /v1/order-subscription-requests/{id}) + * ⚠️ 승인과 반려는 별도 액션이 아니라 approval 값으로 갈린다. + * 서버가 params[:action] 을 Rails 예약어로 쓰기 때문에 키 이름이 approval 이다. + */ export interface OrderSubscriptionRequestUpdateParams { order_subscription_request_history_id: string approval: OrderSubscriptionRequestApprovalAction reason?: string + price?: number + tax_free_price?: number + termination_fee?: number + last_bill_refund_price?: number + final_fee?: number + service_end_at?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string [extra: string]: unknown } diff --git a/src/lib/commerce/types/order-subscription.ts b/src/lib/commerce/types/order-subscription.ts index f99dfaa..412d187 100644 --- a/src/lib/commerce/types/order-subscription.ts +++ b/src/lib/commerce/types/order-subscription.ts @@ -51,21 +51,47 @@ export interface CommerceOrderSubscription { cancel_at?: string } +/** + * 정기구독 목록 조회 파라미터 (GET /v1/order_subscriptions) + * limit 미지정시 서버 기본값과 동일한 20 이 적용된다. + * ⚠️ 날짜 키는 search_date_from / search_date_to (또는 s_at / e_at) 다. orders 의 css_at / cse_at 와 다르다. + */ export interface OrderSubscriptionListParams extends ListParams { + search_date_from?: string + search_date_to?: string s_at?: string e_at?: string request_type?: string user_group_id?: string user_id?: string + status?: number } +/** + * 구독 계약 변경 파라미터 (PUT /v1/order_subscriptions/{order_subscription_id}) + * 바뀐 값만 보내면 된다. 서버가 supervisor scope 를 요구한다. + */ export interface OrderSubscriptionUpdateParams { order_subscription_id: string + product_id?: string + product_option_id?: string + order_name?: string + total_subscription_duration?: number + quantity?: number + address_id?: string + username?: string + phone?: string + email?: string + use_free_trial?: boolean + free_trial_day?: number + service_start_at?: string next_billing_at?: string billing_key?: string status?: number payment_next_at?: string service_end_at?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string } // Request Ing Types @@ -75,12 +101,46 @@ export interface OrderSubscriptionPauseParams { reason?: string paused_at?: string expected_resume_at?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string } export interface OrderSubscriptionResumeParams { order_subscription_id?: string order_number?: string + reason?: string resume_at?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string +} + +/** + * 중도인수 요청 파라미터 (POST /v1/order_subscriptions/requests/ing/purchase) + */ +export interface OrderSubscriptionPurchaseParams { + order_subscription_id?: string + order_number?: string + price?: number + tax_free_price?: number + reason?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string +} + +/** + * 구독 이전/승계 요청 파라미터 (POST /v1/order_subscriptions/requests/ing/transfer) + */ +export interface OrderSubscriptionTransferParams { + order_subscription_id?: string + new_user_id?: string + new_username?: string + new_user_email?: string + new_user_phone?: string + new_user_address?: string + wallet_id?: string + reason?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string } export interface OrderSubscriptionTerminationParams { @@ -91,6 +151,8 @@ export interface OrderSubscriptionTerminationParams { final_fee?: number service_end_at?: string reason?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string } export interface CalcTerminateFeeResponse { diff --git a/src/lib/commerce/types/order.ts b/src/lib/commerce/types/order.ts index a55f258..c351757 100644 --- a/src/lib/commerce/types/order.ts +++ b/src/lib/commerce/types/order.ts @@ -52,12 +52,20 @@ export interface CommerceOrderCancellationRequestHistory { processed_at?: string } +/** + * 주문 목록 조회 파라미터 (GET /v1/orders) + * limit 은 서버 기본 20 · 최대 50 이며, 50 초과를 보내도 서버가 50 으로 클램프한다. + * ⚠️ 날짜 키의 정식 이름은 search_date_from / search_date_to 다. + * css_at / cse_at 는 서버가 받아주는 별칭이며 하위호환을 위해 남겨둔다. + */ export interface OrderListParams extends ListParams { user_id?: string user_group_id?: string status?: number[] payment_status?: number[] cs_type?: string + search_date_from?: string + search_date_to?: string css_at?: string cse_at?: string subscription_billing_type?: number diff --git a/src/lib/commerce/types/product.ts b/src/lib/commerce/types/product.ts index 1dff080..2e8e7b1 100644 --- a/src/lib/commerce/types/product.ts +++ b/src/lib/commerce/types/product.ts @@ -147,9 +147,24 @@ export interface MallProductListParams extends ProductListParams { idempotency_key?: string } +/** + * 상품 판매/노출 상태 변경 파라미터 (PUT /v1/products/{product_id}/status) + * 서버 _status_params 기준. ⚠️ 재고(stock)는 여기가 아니라 update 로 바꾼다. + */ export interface ProductStatusParams { product_id: string - status: number + status?: number status_display?: boolean status_sale?: boolean + status_frozen?: boolean + status_review?: boolean + use_display_period?: boolean + display_start_at?: string + display_end_at?: string + use_sale_period?: boolean + sale_start_at?: string + sale_end_at?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string + [extra: string]: unknown } diff --git a/src/lib/commerce/types/user-group.ts b/src/lib/commerce/types/user-group.ts index 9caa6be..2400440 100644 --- a/src/lib/commerce/types/user-group.ts +++ b/src/lib/commerce/types/user-group.ts @@ -62,17 +62,30 @@ export interface UserGroupListParams extends ListParams { corporate_type?: number } +/** + * 그룹 구매한도 설정 파라미터 (PUT /v1/user-groups/{user_group_id}/limit) + * ⚠️ update 로는 반영되지 않는다 — 서버가 이 값들을 update 에서 제거하기 때문에 전용 라우트로만 바뀐다. + */ export interface UserGroupLimitParams { user_group_id: string use_limit?: boolean + limit_month_purchase?: number + limit_week_purchase?: number + limit_message?: string purchase_limit?: number subscribed_limit?: number - limit_message?: string + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string } +/** + * 그룹 구독 합산청구(정산주기) 설정 파라미터 (PUT /v1/user-groups/{user_group_id}/aggregate-transaction) + */ export interface UserGroupAggregateTransactionParams { user_group_id: string use_subscription_aggregate_transaction?: boolean subscription_month_day?: number subscription_week_day?: number + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string } diff --git a/src/lib/commerce/types/user.ts b/src/lib/commerce/types/user.ts index 83bef3d..9c20e08 100644 --- a/src/lib/commerce/types/user.ts +++ b/src/lib/commerce/types/user.ts @@ -83,8 +83,8 @@ export interface UserLoginResponse { } /** - * 회원 로그인 파라미터 (V1 Mall API) - * POST /v1/user/login + * 회원 로그인 파라미터 (V1 API) + * POST /v1/users/login */ export interface MallUserLoginParams { login_id: string @@ -95,8 +95,8 @@ export interface MallUserLoginParams { } /** - * 회원가입 파라미터 (V1 Mall API) - * POST /v1/user/join + * 회원가입 파라미터 (V1 API) + * POST /v1/users/join */ export interface MallUserJoinParams { login_id: string @@ -114,13 +114,18 @@ export interface MallUserJoinParams { } /** - * 회원가입 중복 확인 타입 (V1 Mall API) - * GET /v1/user/join/{type} + * 회원가입 중복 확인 타입 (V1 API) + * GET /v1/users/join/{type} */ -export type MallUserJoinCheckType = 'email-exist' | 'id-exist' | 'phone-exist' | 'group-business-number-exist' +export type MallUserJoinCheckType = + | 'email-exist' + | 'id-exist' + | 'phone-exist' + | 'uid-exist' + | 'group-business-number-exist' /** - * 회원 세션 조회 응답 (V1 Mall API) + * 회원 세션 조회 응답 (V1 API) */ export interface MallUserSessionResponse { user?: CommerceUser diff --git a/src/lib/commerce/types/webhook.ts b/src/lib/commerce/types/webhook.ts new file mode 100644 index 0000000..d04c608 --- /dev/null +++ b/src/lib/commerce/types/webhook.ts @@ -0,0 +1,9 @@ +/** + * 테스트 웹훅 발송 파라미터 (POST /v1/webhook/test) + */ +export interface SendTestWebhookParams { + /** 웹훅 본문 Content-Type (미지정시 서버 기본값) */ + header_content_type?: number + /** 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다) */ + idempotency_key?: string +} diff --git a/test/commerce/commerceRouteContract.js b/test/commerce/commerceRouteContract.js new file mode 100644 index 0000000..a8421a8 --- /dev/null +++ b/test/commerce/commerceRouteContract.js @@ -0,0 +1,299 @@ +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { BootpayCommerce } = require('../../dist/bootpay-commerce.js'); + +// Commerce API - 라우트/동사/헤더 규약 테스트 (네트워크 호출 없음, 키 불필요) +// +// commerce-api v1 라우트 정본에 맞춘 회귀 테스트다. 특히 아래 함정들을 고정한다. +// · 회원 endpoint 는 복수 users/… 다 (단수 user/… 라우트는 존재하지 않는다) +// · requests/ing 중 resume 만 PUT, 나머지는 POST +// · order_subscriptions · order_subscription_bills 는 언더스코어, +// order-subscription-requests · user-groups 는 하이픈 +// · 조정항목 삭제 대상 ID 는 query 가 아니라 body +// · multipart 전송 시 boundary 가 살아 있어야 한다 (인터셉터가 Content-Type 을 덮어쓰면 안 된다) + +function header(config, name) { + if (!config.headers) return undefined; + if (typeof config.headers.get === 'function') return config.headers.get(name); + return config.headers[name] || config.headers[name.toLowerCase()] || config.headers[name.toUpperCase()]; +} + +function relative(config) { + return config.url.replace('https://dev-api.bootapi.com/v1/', ''); +} + +(async () => { + const commerce = new BootpayCommerce({ client_key: 'ck', secret_key: 'sk', mode: 'development' }); + + const requests = []; + commerce.$http.defaults.adapter = async (config) => { + requests.push(config); + return { data: {}, status: 200, statusText: 'OK', headers: {}, config, request: {} }; + }; + + // 직전 요청을 (method, uri) 로 검증한다. uri 는 query 를 제외한 경로만 비교한다. + const last = () => requests[requests.length - 1]; + async function expect(label, call, method, uri) { + await call(); + const config = last(); + assert.strictEqual(config.method.toLowerCase(), method, `${label}: method`); + assert.strictEqual(relative(config).split('?')[0], uri, `${label}: uri`); + return config; + } + + // ── 회원: 단수 user/… 는 죽은 경로다. 전부 복수 users/… 여야 한다 ── + await expect('userLogin', () => commerce.user.userLogin({ login_id: 'id', password: 'pw' }), 'post', 'users/login'); + await expect('userSession', () => commerce.user.userSession('jwt'), 'get', 'users/session'); + await expect('userLogout', () => commerce.user.userLogout('jwt'), 'delete', 'users/session'); + await expect('userJoin', () => commerce.user.userJoin({ login_id: 'id', password: 'pw', name: '홍길동' }), 'post', 'users/join'); + await expect('userJoinCheck', () => commerce.user.userJoinCheck('email-exist', 'a@b.com'), 'get', 'users/join/email-exist'); + + const uidExist = await expect('uidExist', () => commerce.user.uidExist('ex_uid_1'), 'get', 'users/join/uid-exist'); + assert.ok(relative(uidExist).includes('pk=ex_uid_1'), 'uidExist: pk in query'); + assert.strictEqual(header(uidExist, 'BOOTPAY-ROLE'), 'user'); + + // ── 청구서 ── + const invoiceList = await expect('invoice.list', () => commerce.invoice.list(), 'get', 'invoices'); + assert.ok(relative(invoiceList).includes('limit=24'), 'invoice.list: 서버 기본 limit 24 를 보낸다'); + assert.strictEqual(header(invoiceList, 'BOOTPAY-ROLE'), 'user'); + assert.ok(header(invoiceList, 'Idempotency-Key'), 'invoice.list: Idempotency-Key 자동 생성'); + + const invoiceFiltered = await expect( + 'invoice.list(params)', + () => commerce.invoice.list({ page: 2, limit: 10, cs_type: 'A', user_id: 'u1', product_type: 3, css_at: '2024-01-01', cse_at: '2024-12-31' }), + 'get', + 'invoices' + ); + ['page=2', 'limit=10', 'cs_type=A', 'user_id=u1', 'product_type=3', 'css_at=2024-01-01', 'cse_at=2024-12-31'].forEach((q) => + assert.ok(relative(invoiceFiltered).includes(q), `invoice.list: ${q}`) + ); + + await expect('invoice.detail', () => commerce.invoice.detail('inv1'), 'get', 'invoices/inv1'); + + // send_types 미전달시 body 에 키를 넣지 않는다 (서버가 빈 배열로 처리) + const notifyBare = await expect('invoice.notify', () => commerce.invoice.notify('inv1'), 'post', 'invoices/inv1/notify'); + assert.deepStrictEqual(JSON.parse(notifyBare.data), {}); + const notify = await expect('invoice.notify(send_types)', () => commerce.invoice.notify('inv1', [1, 3]), 'post', 'invoices/inv1/notify'); + assert.deepStrictEqual(JSON.parse(notify.data), { send_types: [1, 3] }); + + // ── 상품 쓰기: manager scope ── + const productCreate = await expect('product.create', () => commerce.product.create({ name: '상품', display_price: 1000 }), 'post', 'products'); + assert.strictEqual(header(productCreate, 'BOOTPAY-ROLE'), 'manager'); + assert.ok(String(header(productCreate, 'Content-Type')).includes('application/json'), 'product.create: 이미지 없으면 JSON'); + assert.deepStrictEqual(JSON.parse(productCreate.data), { name: '상품', display_price: 1000 }); + + const productUpdate = await expect('product.update', () => commerce.product.update({ product_id: 'p1', stock: 5 }), 'put', 'products/p1'); + assert.strictEqual(header(productUpdate, 'BOOTPAY-ROLE'), 'manager'); + + const productStatus = await expect( + 'product.status', + () => commerce.product.status({ product_id: 'p1', status_sale: true, status_display: false }), + 'put', + 'products/p1/status' + ); + assert.strictEqual(header(productStatus, 'BOOTPAY-ROLE'), 'manager'); + assert.deepStrictEqual(JSON.parse(productStatus.data), { status_sale: true, status_display: false }); + + const productDelete = await expect('product.delete', () => commerce.product.delete('p1'), 'delete', 'products/p1'); + assert.strictEqual(header(productDelete, 'BOOTPAY-ROLE'), 'manager'); + + // ── 상품 multipart: boundary 가 살아 있어야 한다 ── + const imagePath = path.join(os.tmpdir(), 'bootpay-commerce-route-contract.png'); + fs.writeFileSync(imagePath, Buffer.from('89504e470d0a1a0a', 'hex')); + try { + const multipart = await expect( + 'product.create(images)', + () => commerce.product.create({ name: '상품', search_tags: ['a', 'b'] }, [imagePath]), + 'post', + 'products' + ); + const contentType = String(header(multipart, 'Content-Type')); + assert.ok(contentType.startsWith('multipart/form-data'), 'multipart Content-Type 이 유지되어야 한다'); + assert.ok(contentType.includes('boundary='), 'boundary 가 유실되면 서버가 본문을 null 로 읽는다'); + assert.strictEqual(header(multipart, 'BOOTPAY-ROLE'), 'manager'); + } finally { + fs.unlinkSync(imagePath); + } + + // ── 주문 / 주문취소 ── + const orderList = await expect( + 'order.list', + () => commerce.order.list({ limit: 50, search_date_from: '2024-01-01', search_date_to: '2024-12-31' }), + 'get', + 'orders' + ); + ['limit=50', 'search_date_from=2024-01-01', 'search_date_to=2024-12-31'].forEach((q) => + assert.ok(relative(orderList).includes(q), `order.list: ${q}`) + ); + + const cancelList = await expect('orderCancel.list', () => commerce.orderCancel.list({ order_number: 'o1' }), 'get', 'order/cancel'); + assert.strictEqual(header(cancelList, 'BOOTPAY-ROLE'), 'user'); + + const withdraw = await expect('orderCancel.withdraw', () => commerce.orderCancel.withdraw('c1'), 'put', 'order/cancel/c1/withdraw'); + assert.strictEqual(header(withdraw, 'BOOTPAY-ROLE'), 'user'); + + const approve = await expect( + 'orderCancel.approve', + () => commerce.orderCancel.approve({ order_cancellation_request_id: 'c1', message: '승인' }), + 'put', + 'order/cancel/c1/approve' + ); + assert.strictEqual(header(approve, 'BOOTPAY-ROLE'), 'supervisor'); + assert.deepStrictEqual(JSON.parse(approve.data), { message: '승인' }); + + // 구 인자명도 계속 받는다 (하위호환) + await expect( + 'orderCancel.approve(legacy id)', + () => commerce.orderCancel.approve({ order_cancel_request_history_id: 'c2' }), + 'put', + 'order/cancel/c2/approve' + ); + await expect( + 'orderCancel.reject', + () => commerce.orderCancel.reject({ order_cancellation_request_id: 'c1', message: '반려' }), + 'put', + 'order/cancel/c1/reject' + ); + + // ── 구독 계약변경 / 조정항목 / 빌 ── + const subscriptionList = await expect( + 'orderSubscription.list', + () => commerce.orderSubscription.list({ limit: 30, status: 1, search_date_from: '2024-01-01' }), + 'get', + 'order_subscriptions' + ); + ['limit=30', 'status=1', 'search_date_from=2024-01-01'].forEach((q) => + assert.ok(relative(subscriptionList).includes(q), `orderSubscription.list: ${q}`) + ); + + const subscriptionUpdate = await expect( + 'orderSubscription.update', + () => commerce.orderSubscription.update({ order_subscription_id: 's1', quantity: 2, order_name: '변경' }), + 'put', + 'order_subscriptions/s1' + ); + assert.strictEqual(header(subscriptionUpdate, 'BOOTPAY-ROLE'), 'supervisor'); + assert.deepStrictEqual(JSON.parse(subscriptionUpdate.data), { quantity: 2, order_name: '변경' }); + + const adjustmentCreate = await expect( + 'adjustment.create', + () => commerce.orderSubscriptionAdjustment.create('s1', { name: '할인' }), + 'post', + 'order_subscriptions/s1/adjustments' + ); + assert.strictEqual(header(adjustmentCreate, 'BOOTPAY-ROLE'), 'supervisor'); + assert.deepStrictEqual(JSON.parse(adjustmentCreate.data), { price: 0, duration: 1, tax_free_price: 0, name: '할인' }); + + const adjustmentUpdate = await expect( + 'adjustment.update', + () => commerce.orderSubscriptionAdjustment.update({ order_subscription_id: 's1', duration: 2, adjustments: [{ name: '할인', price: -1000 }] }), + 'put', + 'order_subscriptions/s1/adjustments' + ); + assert.deepStrictEqual(JSON.parse(adjustmentUpdate.data), { duration: 2, adjustments: [{ name: '할인', price: -1000 }] }); + + // 삭제 대상 ID 는 query 가 아니라 body 로 간다 + const adjustmentDelete = await expect( + 'adjustment.delete', + () => commerce.orderSubscriptionAdjustment.delete('s1', 'a1'), + 'delete', + 'order_subscriptions/s1/adjustments' + ); + assert.ok(!relative(adjustmentDelete).includes('?'), 'adjustment.delete: query 로 보내면 안 된다'); + assert.deepStrictEqual(JSON.parse(adjustmentDelete.data), { order_subscription_adjustment_id: 'a1' }); + assert.strictEqual(header(adjustmentDelete, 'BOOTPAY-ROLE'), 'supervisor'); + + const billList = await expect( + 'orderSubscriptionBill.list', + () => commerce.orderSubscriptionBill.list({ order_subscription_id: 's1' }), + 'get', + 'order_subscription_bills' + ); + assert.ok(relative(billList).includes('limit=20'), 'orderSubscriptionBill.list: 서버 기본 limit 20'); + assert.strictEqual(header(billList, 'BOOTPAY-ROLE'), 'user'); + + // ── 구독 진행중 요청 (requests/ing) — resume 만 PUT ── + const ing = commerce.orderSubscription.requestIng; + await expect('ing.pause', () => ing.pause({ order_subscription_id: 's1' }), 'post', 'order_subscriptions/requests/ing/pause'); + await expect('ing.resume', () => ing.resume({ order_subscription_id: 's1' }), 'put', 'order_subscriptions/requests/ing/resume'); + await expect('ing.purchase', () => ing.purchase({ order_subscription_id: 's1', price: 1000 }), 'post', 'order_subscriptions/requests/ing/purchase'); + await expect('ing.termination', () => ing.termination({ order_subscription_id: 's1' }), 'post', 'order_subscriptions/requests/ing/termination'); + + const transfer = await expect( + 'ing.transfer', + () => ing.transfer({ order_subscription_id: 's1', new_user_id: 'u2' }), + 'post', + 'order_subscriptions/requests/ing/transfer' + ); + assert.strictEqual(header(transfer, 'BOOTPAY-ROLE'), 'user'); + assert.deepStrictEqual(JSON.parse(transfer.data), { order_subscription_id: 's1', new_user_id: 'u2' }); + + const calc = await expect( + 'ing.calculateTerminationFee', + () => ing.calculateTerminationFee('s1', 'o1'), + 'get', + 'order_subscriptions/requests/ing/calculate_termination_fee' + ); + assert.ok(relative(calc).includes('order_subscription_id=s1') && relative(calc).includes('order_number=o1')); + + // ── 구독 요청 리소스 (하이픈 경로) ── + const requestList = await expect('request.list', () => commerce.orderSubscriptionRequest.list(), 'get', 'order-subscription-requests'); + assert.strictEqual(header(requestList, 'BOOTPAY-ROLE'), 'user', 'project_id 가 없으면 user scope'); + + const supervisorList = await expect( + 'request.list(project_id)', + () => commerce.orderSubscriptionRequest.list({ project_id: 'prj1', order_subscription_id: 's1', user_id: 'u1', user_group_id: 'g1' }), + 'get', + 'order-subscription-requests' + ); + assert.strictEqual(header(supervisorList, 'BOOTPAY-ROLE'), 'supervisor', 'project_id 가 있으면 supervisor scope'); + ['project_id=prj1', 'order_subscription_id=s1', 'user_id=u1', 'user_group_id=g1'].forEach((q) => + assert.ok(relative(supervisorList).includes(q), `request.list: ${q}`) + ); + + await expect('request.detail', () => commerce.orderSubscriptionRequest.detail('r1'), 'get', 'order-subscription-requests/r1'); + + const requestUpdate = await expect( + 'request.update', + () => commerce.orderSubscriptionRequest.update({ order_subscription_request_history_id: 'r1', approval: 'approve', reason: '승인' }), + 'put', + 'order-subscription-requests/r1' + ); + assert.strictEqual(header(requestUpdate, 'BOOTPAY-ROLE'), 'supervisor'); + assert.deepStrictEqual(JSON.parse(requestUpdate.data), { approval: 'approve', reason: '승인' }); + + // ── 회원 그룹 (하이픈 경로) ── + const groupLimit = await expect( + 'userGroup.limit', + () => commerce.userGroup.limit({ user_group_id: 'g1', use_limit: true, limit_month_purchase: 1000, limit_week_purchase: 500 }), + 'put', + 'user-groups/g1/limit' + ); + assert.strictEqual(header(groupLimit, 'BOOTPAY-ROLE'), 'manager'); + assert.deepStrictEqual(JSON.parse(groupLimit.data), { + use_limit: true, + limit_month_purchase: 1000, + limit_week_purchase: 500 + }); + + const aggregate = await expect( + 'userGroup.aggregateTransaction', + () => commerce.userGroup.aggregateTransaction({ user_group_id: 'g1', use_subscription_aggregate_transaction: true, subscription_month_day: 5 }), + 'put', + 'user-groups/g1/aggregate-transaction' + ); + assert.strictEqual(header(aggregate, 'BOOTPAY-ROLE'), 'manager'); + + // ── 테스트 웹훅 ── + const webhookBare = await expect('webhook.sendTest', () => commerce.webhook.sendTest(), 'post', 'webhook/test'); + assert.deepStrictEqual(JSON.parse(webhookBare.data), {}); + const webhook = await expect('webhook.sendTest(params)', () => commerce.webhook.sendTest({ header_content_type: 1 }), 'post', 'webhook/test'); + assert.deepStrictEqual(JSON.parse(webhook.data), { header_content_type: 1 }); + + console.log(`commerce route contract: ${requests.length} requests verified (uri · method · role · payload)`); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/test/commerce/orderCancelApprove.js b/test/commerce/orderCancelApprove.js index 2ad3a05..68681cf 100644 --- a/test/commerce/orderCancelApprove.js +++ b/test/commerce/orderCancelApprove.js @@ -15,9 +15,11 @@ const keys = getCommerceKeys(); // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. // await commerce.getAccessToken() + // 정식 인자명은 order_cancellation_request_id — orderCancel.list() 로 얻는다. + // 구 이름 order_cancel_request_history_id 도 하위호환으로 계속 동작한다. const response = await commerce.orderCancel.approve({ - order_cancel_request_history_id: 'ORDER_CANCEL_REQUEST_HISTORY_ID_HERE', - approve_reason: '취소 승인 완료' + order_cancellation_request_id: 'ORDER_CANCELLATION_REQUEST_ID_HERE', + message: '취소 승인 완료' }) console.log('OrderCancel Approve Response:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/orderCancelWithdraw.js b/test/commerce/orderCancelWithdraw.js index 85edf48..0cedbff 100644 --- a/test/commerce/orderCancelWithdraw.js +++ b/test/commerce/orderCancelWithdraw.js @@ -15,8 +15,14 @@ const keys = getCommerceKeys(); // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. // await commerce.getAccessToken() - const response = await commerce.orderCancel.withdraw('ORDER_CANCEL_REQUEST_HISTORY_ID_HERE') + // 문자열로 바로 넘겨도 되고, 객체 형태로 idempotency_key 를 함께 줄 수도 있다. + const response = await commerce.orderCancel.withdraw('ORDER_CANCELLATION_REQUEST_ID_HERE') console.log('OrderCancel Withdraw Response:', JSON.stringify(response, null, 2)) + + const byParams = await commerce.orderCancel.withdraw({ + order_cancellation_request_id: 'ORDER_CANCELLATION_REQUEST_ID_HERE' + }) + console.log('OrderCancel Withdraw (params) Response:', byParams) } catch (e) { console.error('Error:', e) } diff --git a/test/commerce/orderSubscriptionPurchase.js b/test/commerce/orderSubscriptionPurchase.js new file mode 100644 index 0000000..65cb20f --- /dev/null +++ b/test/commerce/orderSubscriptionPurchase.js @@ -0,0 +1,29 @@ +const { getCommerceKeys, COMMERCE_TEST_DATA } = require('../config.js'); +const keys = getCommerceKeys(); +// Commerce API - OrderSubscription 중도인수 요청 테스트 +// POST /v1/order_subscriptions/requests/ing/purchase + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode + }) + + try { + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() + + const response = await commerce.orderSubscription.requestIng.purchase({ + order_subscription_id: COMMERCE_TEST_DATA.order_subscription_id, + price: 100000, + tax_free_price: 0, + reason: '중도인수 요청' + }) + console.log('OrderSubscription Purchase Response:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/orderSubscriptionTransfer.js b/test/commerce/orderSubscriptionTransfer.js new file mode 100644 index 0000000..38c5f9a --- /dev/null +++ b/test/commerce/orderSubscriptionTransfer.js @@ -0,0 +1,31 @@ +const { getCommerceKeys, COMMERCE_TEST_DATA } = require('../config.js'); +const keys = getCommerceKeys(); +// Commerce API - OrderSubscription 이전/승계 요청 테스트 +// POST /v1/order_subscriptions/requests/ing/transfer + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode + }) + + try { + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() + + const response = await commerce.orderSubscription.requestIng.transfer({ + order_subscription_id: COMMERCE_TEST_DATA.order_subscription_id, + new_user_id: COMMERCE_TEST_DATA.user_id, + new_username: '홍길동', + new_user_email: 'test@example.com', + new_user_phone: '01000000000', + reason: '구독 승계 요청' + }) + console.log('OrderSubscription Transfer Response:', JSON.stringify(response, null, 2)) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/userGroupLimit.js b/test/commerce/userGroupLimit.js index d59120c..b16a1fa 100644 --- a/test/commerce/userGroupLimit.js +++ b/test/commerce/userGroupLimit.js @@ -15,10 +15,13 @@ const keys = getCommerceKeys(); // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. // await commerce.getAccessToken() + // ⚠️ userGroup.update 로는 한도가 반영되지 않는다 — 이 전용 라우트로만 바뀐다. const response = await commerce.userGroup.limit({ user_group_id: 'USER_GROUP_ID_HERE', - limit_amount: 1000000, // 제한 금액 - limit_count: 100 // 제한 횟수 + use_limit: true, + limit_month_purchase: 1000000, // 월 구매한도 + limit_week_purchase: 300000, // 주 구매한도 + limit_message: '월 구매한도를 초과했습니다.' }) console.log('UserGroup Limit Response:', JSON.stringify(response, null, 2)) } catch (e) { diff --git a/test/commerce/userMallSessionRequest.js b/test/commerce/userMallSessionRequest.js index e41ac40..f16ff91 100644 --- a/test/commerce/userMallSessionRequest.js +++ b/test/commerce/userMallSessionRequest.js @@ -11,13 +11,14 @@ function body(config) { return typeof config.data === 'string' ? JSON.parse(config.data) : config.data; } -// Commerce API - 쇼핑몰(V1 Mall API) 회원 요청 규약 테스트 (네트워크 호출 없음) -// 1) 로그인: POST user/login (login_id / password / corporate_type) -// 2) 세션 조회: GET user/session (Bootpay-User-JWT) -// 3) 로그아웃: DELETE user/session (Bootpay-User-JWT) -// 4) 회원가입: POST user/join (null/undefined 값은 전송하지 않는다) -// 5) 중복 확인: GET user/join/{type}?pk={pk} -// * 위 endpoint 들은 users/... (레거시/외부 회원 API) 와 별개의 경로다. +// Commerce API - V1 회원 요청 규약 테스트 (네트워크 호출 없음) +// 1) 로그인: POST users/login (login_id / password / corporate_type) +// 2) 세션 조회: GET users/session (Bootpay-User-JWT) +// 3) 로그아웃: DELETE users/session (Bootpay-User-JWT) +// 4) 회원가입: POST users/join (null/undefined 값은 전송하지 않는다) +// 5) 중복 확인: GET users/join/{type}?pk={pk} +// ⚠️ 단수 user/... 는 commerce-api v1 에 존재하지 않는 죽은 경로다. 전부 복수 users/... 여야 한다. +// ⚠️ 로그인은 POST /v1/users/login 이다. POST /v1/users/session 은 라우트만 있고 create 액션이 없다. (async () => { const commerce = new BootpayCommerce({ @@ -45,7 +46,7 @@ function body(config) { password: 'password123' }); assert.strictEqual(requests[0].method.toLowerCase(), 'post'); - assert.strictEqual(requests[0].url, 'https://dev-api.bootapi.com/v1/user/login'); + assert.strictEqual(requests[0].url, 'https://dev-api.bootapi.com/v1/users/login'); assert.deepStrictEqual(body(requests[0]), { login_id: 'test_user@example.com', password: 'password123', @@ -70,7 +71,7 @@ function body(config) { // 3) 회원 세션 조회 — Bootpay-User-JWT 헤더 await commerce.user.userSession('USER_JWT'); assert.strictEqual(requests[2].method.toLowerCase(), 'get'); - assert.strictEqual(requests[2].url, 'https://dev-api.bootapi.com/v1/user/session'); + assert.strictEqual(requests[2].url, 'https://dev-api.bootapi.com/v1/users/session'); assert.strictEqual(header(requests[2], 'Bootpay-User-JWT'), 'USER_JWT'); // 4) user_jwt 가 없으면 헤더를 붙이지 않는다 (Ruby SDK 의 headers.compact 와 동일 동작) @@ -78,10 +79,10 @@ function body(config) { assert.strictEqual(header(requests[3], 'Bootpay-User-JWT'), undefined); assert.ok(header(requests[3], 'Idempotency-Key'), 'Idempotency-Key header is required'); - // 5) 회원 로그아웃 — DELETE user/session + // 5) 회원 로그아웃 — DELETE users/session await commerce.user.userLogout('USER_JWT', 'logout-key'); assert.strictEqual(requests[4].method.toLowerCase(), 'delete'); - assert.strictEqual(requests[4].url, 'https://dev-api.bootapi.com/v1/user/session'); + assert.strictEqual(requests[4].url, 'https://dev-api.bootapi.com/v1/users/session'); assert.strictEqual(header(requests[4], 'Bootpay-User-JWT'), 'USER_JWT'); assert.strictEqual(header(requests[4], 'Idempotency-Key'), 'logout-key'); @@ -96,7 +97,7 @@ function body(config) { gender: null }); assert.strictEqual(requests[5].method.toLowerCase(), 'post'); - assert.strictEqual(requests[5].url, 'https://dev-api.bootapi.com/v1/user/join'); + assert.strictEqual(requests[5].url, 'https://dev-api.bootapi.com/v1/users/join'); assert.deepStrictEqual(body(requests[5]), { login_id: 'test_user@example.com', password: 'password123', @@ -111,18 +112,18 @@ function body(config) { assert.strictEqual(requests[6].method.toLowerCase(), 'get'); assert.strictEqual( requests[6].url, - 'https://dev-api.bootapi.com/v1/user/join/email-exist?pk=test_user%40example.com' + 'https://dev-api.bootapi.com/v1/users/join/email-exist?pk=test_user%40example.com' ); assert.ok(header(requests[6], 'Idempotency-Key'), 'Idempotency-Key header is required'); await commerce.user.userJoinCheck('group-business-number-exist', '123-45-67890', 'check-key'); assert.strictEqual( requests[7].url, - 'https://dev-api.bootapi.com/v1/user/join/group-business-number-exist?pk=123-45-67890' + 'https://dev-api.bootapi.com/v1/users/join/group-business-number-exist?pk=123-45-67890' ); assert.strictEqual(header(requests[7], 'Idempotency-Key'), 'check-key'); - // 8) 레거시 회원 API (users/...) 는 그대로 유지된다 + // 8) 간편 헬퍼(login / checkExist)도 같은 경로를 쓴다 await commerce.user.login('test_user@example.com', 'password123'); assert.strictEqual(requests[8].url, 'https://dev-api.bootapi.com/v1/users/login'); @@ -132,7 +133,17 @@ function body(config) { 'https://dev-api.bootapi.com/v1/users/join/email-exist?pk=test_user%40example.com' ); - console.log('commerce mall user: user/login, user/session, user/join endpoints + JWT header'); + // 9) uid-exist 전용형 — email/id/phone/group-business-number 와 같은 패턴 + await commerce.user.uidExist('external_uid_1234', 'uid-key'); + assert.strictEqual(requests[10].method.toLowerCase(), 'get'); + assert.strictEqual( + requests[10].url, + 'https://dev-api.bootapi.com/v1/users/join/uid-exist?pk=external_uid_1234' + ); + assert.strictEqual(header(requests[10], 'Idempotency-Key'), 'uid-key'); + assert.strictEqual(header(requests[10], 'BOOTPAY-ROLE'), 'user'); + + console.log('commerce v1 user: users/login, users/session, users/join endpoints + JWT header'); })().catch((error) => { console.error(error); process.exit(1); diff --git a/test/commerce/userUidExist.js b/test/commerce/userUidExist.js new file mode 100644 index 0000000..5aad741 --- /dev/null +++ b/test/commerce/userUidExist.js @@ -0,0 +1,28 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); +// Commerce API - User uid-exist (외부 uid 중복검사) 테스트 +// GET /v1/users/join/uid-exist?pk={uid} + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode + }) + + try { + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() + + const response = await commerce.user.uidExist('external_uid_1234') + console.log('User uid-exist Response:', JSON.stringify(response, null, 2)) + + // 일반형(checkExist)으로도 같은 endpoint 를 호출할 수 있다. + const generic = await commerce.user.checkExist('uid-exist', 'external_uid_1234') + console.log('User checkExist(uid-exist) Response:', generic) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/commerce/webhookSendTest.js b/test/commerce/webhookSendTest.js new file mode 100644 index 0000000..1764d7c --- /dev/null +++ b/test/commerce/webhookSendTest.js @@ -0,0 +1,29 @@ +const { getCommerceKeys } = require('../config.js'); +const keys = getCommerceKeys(); +// Commerce API - 테스트 웹훅 발송 테스트 +// POST /v1/webhook/test + +(async () => { + const { BootpayCommerce } = require('../../dist/bootpay-commerce.js') + + const commerce = new BootpayCommerce({ + client_key: keys.client_key, + secret_key: keys.secret_key, + mode: keys.mode + }) + + try { + // (legacy) application_id 방식에서만 필요. ck/sk 는 매 요청 Basic Auth 헤더로 직접 인증되므로 호출 불필요. + // await commerce.getAccessToken() + + // header_content_type 미지정 — 서버 기본값으로 발송 + const response = await commerce.webhook.sendTest() + console.log('Send Test Webhook Response:', JSON.stringify(response, null, 2)) + + // Content-Type 지정 발송 + const withContentType = await commerce.webhook.sendTest({ header_content_type: 1 }) + console.log('Send Test Webhook (content-type) Response:', withContentType) + } catch (e) { + console.error('Error:', e) + } +})() diff --git a/test/pg/lookupSequentialBillingKey.js b/test/pg/lookupSequentialBillingKey.js index 2bbc8ab..f5b6585 100644 --- a/test/pg/lookupSequentialBillingKey.js +++ b/test/pg/lookupSequentialBillingKey.js @@ -1,14 +1,14 @@ const { Bootpay } = require('../../dist/bootpay.js'); const { getActivePgConfig, TEST_DATA } = require('../config.js'); // PG API - 우선순위(순차) 결제 빌링키 조회 -// GET /v2/subscribe/sequential_billing_key/{billing_key}?widget_key={widget_key} +// GET /v2/subscribe/sequential_billing_key/{billing_key}?widget_key={widget_key}&user_id={user_id} (async () => { Bootpay.setConfiguration(getActivePgConfig()); try { // legacy 모드에서만 실제 토큰 발급. ck/sk 모드에서는 no-op. await Bootpay.getAccessToken(); - const response = await Bootpay.lookupSequentialBillingKey(TEST_DATA.widget_key, TEST_DATA.billing_key_2); + const response = await Bootpay.lookupSequentialBillingKey(TEST_DATA.widget_key, TEST_DATA.billing_key_2, TEST_DATA.user_id); console.log(response); } catch (e) { console.log(e); diff --git a/test/pg/lookupSequentialBillingKeyRequest.js b/test/pg/lookupSequentialBillingKeyRequest.js index efa0fb9..4e02727 100644 --- a/test/pg/lookupSequentialBillingKeyRequest.js +++ b/test/pg/lookupSequentialBillingKeyRequest.js @@ -2,7 +2,7 @@ const assert = require('assert'); const { Bootpay } = require('../../dist/bootpay.js'); // PG API - 우선순위(순차) 결제 빌링키 조회 URL 규약 테스트 (네트워크 호출 없음) -// GET subscribe/sequential_billing_key/{billing_key}?widget_key={widget_key} +// GET subscribe/sequential_billing_key/{billing_key}?widget_key={widget_key}&user_id={user_id} (async () => { Bootpay.setConfiguration({ client_key: 'ck', secret_key: 'sk', mode: 'development' }); @@ -20,14 +20,14 @@ const { Bootpay } = require('../../dist/bootpay.js'); }; }; - await Bootpay.lookupSequentialBillingKey('widget_key_1', 'billing_key_1'); + await Bootpay.lookupSequentialBillingKey('widget_key_1', 'billing_key_1', 'user_id_1'); assert.strictEqual(requests[0].method.toLowerCase(), 'get'); assert.strictEqual( requests[0].url, - 'https://dev-api.bootpay.co.kr/v2/subscribe/sequential_billing_key/billing_key_1?widget_key=widget_key_1' + 'https://dev-api.bootpay.co.kr/v2/subscribe/sequential_billing_key/billing_key_1?widget_key=widget_key_1&user_id=user_id_1' ); - console.log('pg lookupSequentialBillingKey: billing_key in path, widget_key in query'); + console.log('pg lookupSequentialBillingKey: billing_key in path, widget_key/user_id in query'); })().catch((error) => { console.error(error); process.exit(1); diff --git a/test/test.md b/test/test.md index 49bf604..755ebee 100644 --- a/test/test.md +++ b/test/test.md @@ -88,16 +88,36 @@ node test/commerce/mallSettingUpdate.js node test/commerce/orderSubscriptionChargeRequest.js node test/commerce/mallSettingRequest.js -# 쇼핑몰(V1 Mall API) 회원 세션 / 상품 / 가맹점 요청 규약 검증 (네트워크 호출 없음, 키 불필요) +# V1 회원 세션 / 상품 / 가맹점 요청 규약 검증 (네트워크 호출 없음, 키 불필요) node test/commerce/userMallSessionRequest.js node test/commerce/productMallRequest.js node test/commerce/storeRequest.js + +# Commerce 라우트/동사/헤더 규약 일괄 검증 (네트워크 호출 없음, 키 불필요) +node test/commerce/commerceRouteContract.js + +# 외부 uid 중복검사 / 테스트 웹훅 발송 +node test/commerce/userUidExist.js +node test/commerce/webhookSendTest.js + +# 구독 중도인수 / 이전·승계 요청 +node test/commerce/orderSubscriptionPurchase.js +node test/commerce/orderSubscriptionTransfer.js ``` -### 쇼핑몰(V1 Mall API) 회원 endpoint 주의 +### V1 회원 endpoint 주의 + +`user.userLogin / userSession / userLogout / userJoin / userJoinCheck` 는 모두 복수형 `users/...` 경로를 사용한다. +단수형 `user/...` 는 commerce-api v1 에 존재하지 않는 죽은 경로다 — 예전 SDK 가 그리로 보내고 있었다. + +- 로그인은 `POST /v1/users/login` 이다. `POST /v1/users/session` 은 라우트만 있고 `create` 액션이 없으므로 쓰면 안 된다. +- `userJoin` 과 `join`, `userJoinCheck` 와 `checkExist` 는 같은 endpoint 를 부르지만 서버가 파라미터 조합으로 분기하므로 둘 다 유지한다. + +### 라우트 표기 주의 -`user.userLogin / userSession / userLogout / userJoin / userJoinCheck` 는 단수형 `user/...` 경로를 사용하는 쇼핑몰 회원 API 다. -기존 `user.login / join / checkExist` 가 쓰는 복수형 `users/...` (외부 회원 연동 API) 와는 다른 endpoint 이므로 서로 대체할 수 없다. +- 언더스코어: `order_subscriptions`, `order_subscription_bills` +- 하이픈: `order-subscription-requests`, `user-groups` +- `requests/ing` 계열은 `resume` 만 `PUT` 이고 나머지(`pause`/`purchase`/`termination`/`transfer`)는 `POST` 다. 세션이 필요한 호출에는 로그인시 받은 JWT 를 `Bootpay-User-JWT` 헤더로 전달한다. ## 테스트 데이터 @@ -195,3 +215,4 @@ Bootpay.setConfiguration(getActivePgConfig()); - `test/pg/getAccessToken.js` - `test/legacyCompatibility.js` - `test/commerce/authorizationHeader.js` (Commerce — 실제 통신 없이 mock adapter 로 헤더만 검증) +- `test/commerce/commerceRouteContract.js` (Commerce — 실제 통신 없이 mock adapter 로 라우트/동사/role 검증)