diff --git a/.gitignore b/.gitignore index ee0b027..4304d78 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,4 @@ jspm_packages node_modules/* package-lock.json yarn.lock - +dist diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..82f4082 --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +src/* +.gitattributes +.gitignore +package-lock.json +*.iml +*.idea +yarn.lock +tsconfig.json \ No newline at end of file diff --git a/README.md b/README.md index 1ed00ed..e9601a7 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,100 @@ +# Bootpay Server Rest Client [![alt text](https://cdn.bootpay.co.kr/icon/npm.svg)](https://www.npmjs.com/package/@bootpay/server-rest-client) -## PG Analytics - 결제데이터 분석서비스 -* 기존 PG 사를 이용 중이신 사업자도 별도의 계약없이 부트페이를 통해 결제 연동과 통계를 무료로 이용하실 수 있습니다. -* 한줄의 소스코드로 인사이트를 얻어 매출을 극대화하세요. +### 1.0.9 ( Stable ) +* 결제 취소시 taxFree 값 추가 +### 1.0.8 -## 결제 검증 및 취소 - 서버사이드용 -* 보안상의 이유로 결제검증과 취소는 서버사이드에서 이루어집니다. -* 부트페이 서버와 통신시 Rest용 Application Id, Private Key 값을 보내주셔야 하며, 보내실 서버의 IP는 미리 등록하셔야 합니다. +readme 업데이트 +precompile 옵션 변경 -## npm을 통해 restler를 설치합니다 -``` -npm install restler -``` +### 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로도 코딩이 가능합니다. ## 샘플 코드 + +### NPM으로 다운 받은 경우 + ```nodejs -var Bootpay = require('./bootpay'); - -BootpayRest.setConfig( - '[[ REST용 application id ]]', - '[[ Private Key ]]' -); - -BootpayRest.getAccessToken().then(function (tokenData) { - if (tokenData.status === 200) { - BootpayRest.verify('1234') - .then(function (data) { - console.log(data); - }); - } else { - console.log('error!') +const RestClient = require('@bootpay/server-rest-client').RestClient + +// or + +import { RestClient } from '@bootpay/server-rest-client' + +RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' +) +RestClient.getAccessToken().then( + function(response) { + console.log(response) + }, function(e) { + console.log(e) } +) }); +``` + +### github으로 바로 다운 받은 경우 + +먼저 패키지를 모두 설치합니다 -### 더 자세한 정보는 [Docs](https://docs.bootpay.co.kr/api/validate?languageCurrentIndex=2)를 참조해주세요.  +```bash +yarn install +``` + +이후 빌드를 해서 dist로 js로 컴파일 합니다. + +```bash +npm run build +``` + +그리고 dist로 output 된 패키지를 상대 경로로 가져와서 사용합니다. + +```nodejs +const RestClient = require('./dist/bootpay').RestClient + +// or + +import { RestClient } from './dist/bootpay' + +RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' +) +RestClient.getAccessToken().then( + function(response) { + console.log(response) + }, function(e) { + console.log(e) + } +) +}); \ No newline at end of file diff --git a/hello.js b/hello.js deleted file mode 100644 index 2788991..0000000 --- a/hello.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Created by ehowlsla on 2017. 8. 3.. - */ -var Bootpay = require('./bootpay'); - -var bootpay = new Bootpay('application_id_value_1234', '593f8febe13f332431a8ddaw'); -// -bootpay.confirm('593f8febe13f332431a8ddae', function(data) { - console.log(data); -}); - -bootpay.cancel('593f8febe13f332431a8ddae', '관리자 홍길동', '구매자 단순변심', function(data) { - console.log(data); -}); diff --git a/lib/bootpay.js b/lib/bootpay.js deleted file mode 100644 index 4d47c33..0000000 --- a/lib/bootpay.js +++ /dev/null @@ -1,350 +0,0 @@ -/** - * Created by ehowlsla on 2017. 8. 3.. - */ -// npm install restler -var rest = require('restler'); - -module.exports = { - BASE_URL: { - development: 'https://dev-api.bootpay.co.kr', - stage: 'https://stage-api.bootpay.co.kr', - production: 'https://api.bootpay.co.kr' - }, - applicationId: undefined, - privateKey: undefined, - mode: 'production', - token: undefined, - getUrl: function (uri = []) { - return [].concat([this.BASE_URL[this.mode]]).concat(uri).join('/'); - }, - setConfig: function (applicationId, privateKey, mode = 'production') { - this.applicationId = applicationId; - this.privateKey = privateKey; - this.mode = mode; - }, - getAccessToken: function () { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['request', 'token.json']), - { - data: { - application_id: _this.applicationId, - private_key: _this.privateKey - }, - headers: {'Accept': 'application/json', 'Content-Type': 'application/json'} - } - ).on('complete', function (data, response) { - if (data.status === 200) { - _this.token = data.data.token; - resolve(data); - } else { - reject(data); - } - }); - }); - }, - verify: function (receiptId) { - if (receiptId === undefined) throw new Error('receiptId 값을 입력해주세요.'); - if (this.token === undefined || !this.token.length) throw new Error('Access Token을 발급 받은 후 진행해주세요.'); - let _this = this; - return new Promise(function (resolve, reject) { - rest.get( - _this.getUrl(['receipt', receiptId + '.json']), - { - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - submit: function (receiptId) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['submit.json']), - { - data: { - receipt_id: receiptId, - }, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - cancel: function (receiptId, price = undefined, name = undefined, reason = undefined) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['cancel.json']), - { - data: { - receipt_id: receiptId, - price: price, - name: name, - reason: reason - }, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - subscribeBilling: function (billingKey, itemName, price, orderId, items = [], user_info = {}) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['subscribe', 'billing.json']), - { - data: { - billing_key: billingKey, - item_name: itemName, - price: price, - order_id: orderId, - items: items, - username: user_info.username, - phone: user_info.phone, - address: user_info.address - }, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - subscribeBillingReserve: function (billingKey, itemName, price, orderId, execute_at, feedback_url, items = []) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['subscribe', 'billing', 'reserve.json']), - { - data: { - billing_key: billingKey, - item_name: itemName, - price: price, - order_id: orderId, - items: items, - scheduler_type: 'oneshot', - execute_at: execute_at, - feedback_url: feedback_url - }, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - getSubscribeBillingKey: function (data) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['request', 'card_rebill.json']), - { - data: { - order_id: data.orderId, - pg: data.pg, - item_name: data.name, - 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: data.extra - }, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8"', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - destroySubscribeBillingKey: function (billingKey) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.del( - _this.getUrl(['subscribe', 'billing', billingKey]), - { - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8"', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - destroySubscribeBillingReserveCancel: function (billingKey) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.del( - _this.getUrl(['subscribe', 'billing', 'reserve', billingKey]), - { - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8"', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - certificate: function (receiptId) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.get( - _this.getUrl(['certificate', receiptId]), - { - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8"', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - remoteForm: function (remoteForm, smsPayload = {}) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['app', 'rest', 'remote_form.json']), - { - data: { - application_id: _this.applicationId, - remote_form: remoteForm, - sms_payload: smsPayload - }, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8"', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - requestPayment: function (data) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['request', 'payment.json']), - { - data: data, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8"', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - sendSms: function (receiveNumbers, message, sendNumber = undefined, extra = {}) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['push', 'sms.json']), - { - data: { - sp: sendNumber, - rps: receiveNumbers, - msg: message, - m_id: extra.m_id, - o_id: extra.o_id - }, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8"', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - sendLms: function (receiveNumbers, message, subject, sendNumber = undefined, extra = {}) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['push', 'lms.json']), - { - data: { - sp: sendNumber, - rps: receiveNumbers, - msg: message, - sj: subject, - m_id: extra.m_id, - o_id: extra.o_id - }, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8"', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - }, - getUserToken: function (data) { - let _this = this; - return new Promise(function (resolve, reject) { - rest.post( - _this.getUrl(['request', 'user', 'token.json']), - { - data: data, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8"', - 'Authorization': _this.token - } - } - ).on('complete', function (data, response) { - resolve(data); - }); - }); - } -}; \ No newline at end of file diff --git a/package.json b/package.json index 3a43f23..03c8bc0 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,20 @@ { - "name": "bootpay-rest-client", - "version": "1.0.5", - "description": "Bootpay Rest Client Javasrcipt Library", - "main": "lib/bootpay.js", + "name": "@bootpay/server-rest-client", + "version": "1.0.9", + "description": "Bootpay Server Rest Client Javasrcipt Library", + "main": "dist/bootpay.js", + "types": "dist/bootpay.d.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc --build", + "clear": "tsc --build --clean" + }, "dependencies": { - "restler": "^3.4.0", - "babel-cli": "^6.26.0" + "axios": "^0.21.1" }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "devDependencies": { + "ts-node": "^9.0.0", + "typescript": "^4.0.5" }, "repository": { "type": "git", @@ -24,7 +30,7 @@ "bootpay" ], "author": "Bootpay", - "license": "ISC", + "license": "MIT", "bugs": { "url": "https://github.com/bootpay/server_nodejs/issues" }, diff --git a/src/bootpay.ts b/src/bootpay.ts new file mode 100644 index 0000000..60e222d --- /dev/null +++ b/src/bootpay.ts @@ -0,0 +1,500 @@ +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 + price?: number + taxFree?: number + name?: string + reason?: string + refund?: BootpayRefundData +} + +export interface BootpayRefundData { + account: string + accountholder: string + bankcode: string +} + +export interface BootpaySubscribeBillingData { + orderId: string, + pg: string, + itemName: string, + cardNo: string, + cardPw: string + 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, + 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, + items?: Array, + feedbackUrl: string, + feedbackContentType: string // content-type - json, urlencoded +} + +export interface BootpayRequestPaymentData { + pg?: string, + method?: string, + methods?: Array, + orderId: string, + price: number, + taxFree: number, + itemName: string, + returnUrl?: string, + params: any + userInfo?: BootpayUserInfoData, + items?: Array, + extra?: any +} + +export interface BootpayRequestUserTokenData { + userId: string + email?: string + name?: string, + gender?: number // 0 - 여자, 1 - 남자 + birth?: string + phone?: string +} + +export interface BootpayItemData { + unique: string, + qty: number, + itemName: string, + price: number + cat1?: string + cat2?: string + cat3?: string +} + +export interface BootpaySubscribeExtraData { + subscribeTestPayment: number + rawData?: number +} + +export interface BootpayUserInfoData { + id: string, + username: string, + email: string, + phone: string, + gender: number, + area: string +} + + +class BootpayRestClient extends BootpaySingleton { + + $http: AxiosInstance + $token?: string + applicationId?: string + privateKey?: string + mode: string + + 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 + */ + 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 + } + + /** + * 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) + } + + /** + * 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) + } + + /** + * Server Submit method + * Comment by rumi + * @date: 2020-10-27 + * @param receiptId + * @returns Promise + */ + async submit(receiptId: string): Promise { + let response: BootpayCommonResponse + try { + response = await this.$http.post( + this.getApiUrl('submit'), + { receipt_id: receiptId } + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + /** + * Payment Cancel + * Comment by rumi + * @date: 2020-10-27 + * @param data: BootpayCancelData + * @returns Promise + */ + async cancel(data: BootpayCancelData) { + let response: BootpayCommonResponse + try { + response = await this.$http.post( + this.getApiUrl('cancel'), + { + receipt_id: data.receiptId, + price: data.price, + tax_free: data.taxFree, + name: data.name, + reason: data.reason, + refund: data.refund + } + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + /** + * Request Subscribe Card Billing Key + * Comment by rumi + * @date: 2020-10-27 + * @param data: BootpaySubscribeBillingData + * @returns Promise + */ + async requestSubscribeBillingKey(data: BootpaySubscribeBillingData) { + let response: BootpayCommonResponse + 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) + } + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + /** + * destroy billing key + * Comment by rumi + * @date: 2020-10-27 + * @param billingKey: string + * @returns Promise + */ + async destroySubscribeBillingKey(billingKey: string) { + let response: BootpayCommonResponse + try { + response = await this.$http.delete( + this.getApiUrl(`subscribe/billing/${ billingKey }`) + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + /** + * subscribe payment by billing key + * Comment by rumi + * @date: 2020-10-27 + * @param data: BootpayRequestSubscribeBillingPaymentData + * @returns Promise + */ + async requestSubscribeBillingPayment(data: BootpayRequestSubscribeBillingPaymentData) { + let response: BootpayCommonResponse + 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 + } + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + /** + * reserve payment by billing key + * Comment by rumi + * @date: 2020-10-27 + * @param data: BootpayReserveSubscribeBillingData + * @returns Promise + */ + async reserveSubscribeBilling(data: BootpayReserveSubscribeBillingData) { + let response: BootpayCommonResponse + 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 + } + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + /** + * Cancel Reserve Subscribe Billing + * Comment by rumi + * @date: 2020-10-27 + * @param reserveId: string + * @returns Promise + */ + async destroyReserveSubscribeBilling(reserveId: string) { + let response: BootpayCommonResponse + try { + response = await this.$http.delete( + this.getApiUrl(`subscribe/billing/reserve/${ reserveId }`) + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + /** + * Certificate Data + * Comment by rumi + * @date: 2020-10-27 + * @param receiptId: string + * @returns Promise + */ + async certificate(receiptId: string) { + let response: BootpayCommonResponse + try { + response = await this.$http.get( + this.getApiUrl(`certificate/${ receiptId }`) + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + /** + * REST API로 결제 요청을 합니다 + * Comment by rumi + * @date: 2020-10-27 + * @param data: any + * @returns Promise + */ + async requestPayment(data: BootpayRequestPaymentData) { + let response: BootpayCommonResponse + 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) + } + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + /** + * get user token + * Comment by rumi + * @date: 2020-10-27 + * @param data: BootpayRequestUserTokenData + * @returns Promise + */ + async requestUserToken(data: BootpayRequestUserTokenData) { + let response: BootpayCommonResponse + 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 + } + ) + } catch (e) { + return Promise.reject(e) + } + return Promise.resolve(response) + } + + private getApiUrl(uri: string) { + return [API_URL[this.mode], uri].join('/') + } +} + +export const RestClient = BootpayRestClient.currentInstance() \ No newline at end of file diff --git a/src/lib/bootpay/singleton.ts b/src/lib/bootpay/singleton.ts new file mode 100644 index 0000000..eb14325 --- /dev/null +++ b/src/lib/bootpay/singleton.ts @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000..b803a8a --- /dev/null +++ b/src/lib/bootpay/support.ts @@ -0,0 +1,83 @@ +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/test/access_token.js b/test/access_token.js index f45a0ea..b2ab4b2 100644 --- a/test/access_token.js +++ b/test/access_token.js @@ -1,12 +1,10 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'FQj3jOvQYp053nxzWxHSuw+cq3zUlSWZV2ec/8fkiyA=' -); - - -BootpayRest.getAccessToken() -.then(function(data) { - console.log(data); -}); \ No newline at end of file +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + let response = await RestClient.getAccessToken() + console.log(response) +})() diff --git a/test/axios_test.js b/test/axios_test.js new file mode 100644 index 0000000..52880d4 --- /dev/null +++ b/test/axios_test.js @@ -0,0 +1,21 @@ + +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + 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: '59bfc738e13f337dbd6ca48a', + private_key: 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' + }) + console.log(response) + } catch(e) { + console.log(e.response.data) + } +})() \ No newline at end of file diff --git a/test/cancel.js b/test/cancel.js index 80383b3..a38647d 100644 --- a/test/cancel.js +++ b/test/cancel.js @@ -1,13 +1,24 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' -); - - -BootpayRest.getAccessToken().then(function (data) { - BootpayRest.cancel('5b0df1b8e13f332c6c83df6a', 1000, '취소함', '취소합니다').then(function (data) { - console.log(data); - }); -}); \ No newline at end of file +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + let token = await RestClient.getAccessToken() + if (token.status === 200) { + let response + try { + response = await RestClient.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/certificate.js b/test/certificate.js index 6837919..8e3dee5 100644 --- a/test/certificate.js +++ b/test/certificate.js @@ -1,18 +1,18 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' -); - - -BootpayRest.getAccessToken().then(function (tokenData) { - if (tokenData.status === 200) { - BootpayRest.certificate('1234') - .then(function (data) { - console.log(data); - }); - } else { - console.log('error!') +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + let token = await RestClient.getAccessToken() + if (token.status === 200) { + let response + try { + response = await RestClient.certificate('1234') + } catch (e) { + return console.log(e) + } + console.log(response) } -}); \ No newline at end of file +})() \ No newline at end of file diff --git a/test/destroy_subscribe_billing_key.js b/test/destroy_subscribe_billing_key.js new file mode 100644 index 0000000..cb4e82d --- /dev/null +++ b/test/destroy_subscribe_billing_key.js @@ -0,0 +1,18 @@ +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + let token = await RestClient.getAccessToken() + if (token.status === 200) { + let response + try { + response = await RestClient.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 index 276c082..12e3371 100644 --- a/test/get_user_token.js +++ b/test/get_user_token.js @@ -1,23 +1,20 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' -); - -BootpayRest.getAccessToken().then(function (tokenData) { - if (tokenData.status === 200) { - BootpayRest.getUserToken({ - user_id: '[[ user_id ]] ( 필수 )', - email: 'bootpay@bootpay.co.kr', // 필수 - name: '[[ 사용자 명 ]] ( 선택 )', - gender: '[[ 0 - 여자, 1 - 남자 ]] ( 선택 )', - birth: ' [[ 생년월일 ( 6자리) ]] ( 선택 )', - phone: ' [[ 전화번호 ]] ( 페이앱의 경우 필수 )' - }).then(function (data) { - console.log(data); - }); - } else { - console.log('error!') +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + const token = await RestClient.getAccessToken() + if (token.status === 200) { + let result + try { + result = await RestClient.requestUserToken({ + userId: 'gosomi' + }) + } catch (e) { + return console.log(e) + } + console.log(result) } -}); \ No newline at end of file +})() \ No newline at end of file diff --git a/test/request_payment.js b/test/request_payment.js index f03ea52..083868e 100644 --- a/test/request_payment.js +++ b/test/request_payment.js @@ -1,27 +1,28 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' -); - - -BootpayRest.getAccessToken().then(function (tokenData) { - if (tokenData.status === 200) { - BootpayRest.requestPayment({ - pg: 'kcp', - method: 'card', - order_id: (new Date).getTime(), - price: 1000, - name: '테스트 부트페이 상품', - return_url: 'https://dev-api.bootpay.co.kr/callback', - extra: { - expire: 30 - } - }).then(function (data) { - console.log(data); - }); - } else { - console.log('error!') +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + const token = await RestClient.getAccessToken() + if (token.status === 200) { + let result + try { + result = await RestClient.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 +})() \ No newline at end of file diff --git a/test/request_subscribe_rest.js b/test/request_subscribe_rest.js index b18c82f..73ad434 100644 --- a/test/request_subscribe_rest.js +++ b/test/request_subscribe_rest.js @@ -1,28 +1,30 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' -); - - -BootpayRest.getAccessToken().then(function (data) { - BootpayRest.getSubscribeBillingKey({ - orderId: (new Date()).getTime(), - pg: 'nicepay', - name: '정기결제 30일권', - cardNo: '[ 카드 번호 ]', - cardPw: '[ 카드 비밀번호 앞 2자리 ]', - expireYear: '[ 카드 만료 연도 ]', - expireMonth: '[ 카드 만료 월 ]', - identifyNumber: '[ 카드 소유주 생년월일 혹은 법인 번호 ]', - extra: { - subscribe_test_payment: 1 // 100원 결제 후 결제가 되면 billing key를 발행, 결제가 실패하면 에러 +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + let token = await RestClient.getAccessToken() + if (token.status === 200) { + let response + try { + response = await RestClient.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) } - }).then(function (response) { - console.log(response); - // 발급 받은 키를 삭제하는 로직입니다. - BootpayRest.destroySubscribeBillingKey(response.data.billing_key); - }); - -}); \ No newline at end of file + console.log(response) + } +})() \ No newline at end of file diff --git a/test/subscribe_billing.js b/test/subscribe_billing.js index 05948f3..3660fb2 100644 --- a/test/subscribe_billing.js +++ b/test/subscribe_billing.js @@ -1,27 +1,25 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' -); - - -BootpayRest.getAccessToken() - .then( - // Access Token을 가져왔을 때 처리 - function (data) { - BootpayRest.subscribeBilling('5b025b33e13f33310ce560fb', '정기결제입니다.', 1000, (new Date()).getTime(), [], { - username: '홍길동', - phone: '010-0000-0000', - address: '서울특별시 구로구' - }).then(function (data) { - console.log(data); - }, function (data) { - console.log(data); - }); - }, - // Access Token을 가져오는데 실패한 경우 - function (data) { - console.log(data); +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + let token = await RestClient.getAccessToken() + if (token.status === 200) { + let response + try { + response = await RestClient.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) } - ); \ No newline at end of file + console.log(response) + } +})() \ No newline at end of file diff --git a/test/subscribe_billing_reserve.js b/test/subscribe_billing_reserve.js index 19091b1..099b1d8 100644 --- a/test/subscribe_billing_reserve.js +++ b/test/subscribe_billing_reserve.js @@ -1,21 +1,31 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' -); - - -BootpayRest.getAccessToken() - .then(function (data) { - BootpayRest.subscribeBillingReserve( - '5b025b33e13f33310ce560fb', - '정기결제입니다.', - 1000, - (new Date()).getTime(), - parseInt(new Date().getTime() / 1000) + 3600, // 1시간 뒤 실행 - "https://dev-api.bootpay.co.kr/callback" - ).then(function (data) { - console.log(data); - }); - }); \ No newline at end of file +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + let token = await RestClient.getAccessToken() + if (token.status === 200) { + let response + try { + response = await RestClient.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 index 087a223..150338e 100644 --- a/test/subscribe_billing_reserve_cancel.js +++ b/test/subscribe_billing_reserve_cancel.js @@ -1,27 +1,35 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' -); - - -BootpayRest.getAccessToken() - .then(function (data) { - BootpayRest.subscribeBillingReserve( - '5b025b33e13f33310ce560fb', - '정기결제입니다.', - 1000, - (new Date()).getTime(), - parseInt(new Date().getTime() / 1000) + 3600, // 1시간 뒤 실행 - "https://dev-api.bootpay.co.kr/callback" - ).then(function (data) { - console.log(data); - // 예약된 빌링키 취소 - BootpayRest.destroySubscribeBillingReserveCancel(data.reserve_id).then( - function(data) { - console.log(data) - } - ) - }); - }); \ No newline at end of file +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + let token = await RestClient.getAccessToken() + if (token.status === 200) { + let response + try { + response = await RestClient.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 RestClient.destroyReserveSubscribeBilling(response.data.reserve_id) + console.log(response) + } + } 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 index 9f13e6a..3946af6 100644 --- a/test/verify.js +++ b/test/verify.js @@ -1,18 +1,18 @@ -var BootpayRest = require('../lib/bootpay'); - -BootpayRest.setConfig( - '59bfc738e13f337dbd6ca48a', - 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=' -); - - -BootpayRest.getAccessToken().then(function (tokenData) { - if (tokenData.status === 200) { - BootpayRest.verify('1234') - .then(function (data) { - console.log(data); - }); - } else { - console.log('error!') +(async () => { + const RestClient = require('../dist/bootpay').RestClient + RestClient.setConfig( + '59bfc738e13f337dbd6ca48a', + 'pDc0NwlkEX3aSaHTp/PPL/i8vn5E/CqRChgyEp/gHD0=', + 'development' + ) + const token = await RestClient.getAccessToken() + if (token.status === 200) { + let result + try { + result = await RestClient.verify('5f0d42a7d111902931bea5ff') + } catch (e) { + return console.log(e) + } + console.log(result) } -}); \ No newline at end of file +})() \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bf25cbc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "es6" + ], + "module": "commonjs", + "moduleResolution": "node", + "declaration": true, + "outDir": "./dist", + "strict": true, + "baseUrl": "./src", + "paths": { + "*": [ + "../node_modules/*", + "./*" + ] + } + }, + "exclude": [ + "**/*.spec.ts", + "node_modules", + "dist" + ], + "compileOnSave": false +} \ No newline at end of file