├── .github └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .npmrc ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── babel.config.json ├── codecov.yml ├── docs └── images │ └── credit-card.png ├── example └── naver │ └── printPaymentHistory.ts ├── package.json ├── src ├── app │ ├── common │ │ ├── index.ts │ │ ├── module.ts │ │ └── types │ │ │ ├── paymentHistory.d.ts │ │ │ └── response.d.ts │ └── naver │ │ ├── index.ts │ │ ├── module.ts │ │ ├── moduleFactory.ts │ │ ├── pageInteractor.ts │ │ ├── parser.spec.ts │ │ ├── parser.ts │ │ ├── scraper.test.ts │ │ ├── scraper.ts │ │ ├── service.ts │ │ ├── testFixture.ts │ │ ├── types │ │ ├── paymentHistoryResponse.d.ts │ │ ├── serviceGroup.ts │ │ └── statusGroup.ts │ │ ├── urlChanger.test.ts │ │ └── urlChanger.ts └── index.ts ├── tsconfig.json └── yarn.lock /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup Node 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version: '16' 20 | 21 | - name: Install Dependencies 22 | run: yarn --immutable 23 | env: 24 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 25 | # because of .npmrc for supporting npm publish using github action 26 | 27 | - name: Test 28 | run: yarn test:cov 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | # because of .npmrc for supporting npm publish using github action 32 | 33 | - name: Upload Code Coverage 34 | run: bash <(curl -s https://codecov.io/bash) 35 | env: 36 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-node@v2 11 | with: 12 | node-version: '16.x' 13 | registry-url: 'https://registry.npmjs.org' 14 | - run: yarn 15 | - run: yarn build 16 | - run: yarn publish 17 | env: 18 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/macos,node,git,yarn 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,node,git,yarn 4 | 5 | ### Git ### 6 | # Created by git for backups. To disable backups in Git: 7 | # $ git config --global mergetool.keepBackup false 8 | *.orig 9 | 10 | # Created by git when using merge tools for conflicts 11 | *.BACKUP.* 12 | *.BASE.* 13 | *.LOCAL.* 14 | *.REMOTE.* 15 | *_BACKUP_*.txt 16 | *_BASE_*.txt 17 | *_LOCAL_*.txt 18 | *_REMOTE_*.txt 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | 30 | # Thumbnails 31 | ._* 32 | 33 | # Files that might appear in the root of a volume 34 | .DocumentRevisions-V100 35 | .fseventsd 36 | .Spotlight-V100 37 | .TemporaryItems 38 | .Trashes 39 | .VolumeIcon.icns 40 | .com.apple.timemachine.donotpresent 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | ### Node ### 50 | # Logs 51 | logs 52 | *.log 53 | npm-debug.log* 54 | yarn-debug.log* 55 | yarn-error.log* 56 | lerna-debug.log* 57 | .pnpm-debug.log* 58 | 59 | # Diagnostic reports (https://nodejs.org/api/report.html) 60 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 61 | 62 | # Runtime data 63 | pids 64 | *.pid 65 | *.seed 66 | *.pid.lock 67 | 68 | # Directory for instrumented libs generated by jscoverage/JSCover 69 | lib-cov 70 | 71 | # Coverage directory used by tools like istanbul 72 | coverage 73 | *.lcov 74 | 75 | # nyc test coverage 76 | .nyc_output 77 | 78 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 79 | .grunt 80 | 81 | # Bower dependency directory (https://bower.io/) 82 | bower_components 83 | 84 | # node-waf configuration 85 | .lock-wscript 86 | 87 | # Compiled binary addons (https://nodejs.org/api/addons.html) 88 | build/Release 89 | 90 | # Dependency directories 91 | node_modules/ 92 | jspm_packages/ 93 | 94 | # Snowpack dependency directory (https://snowpack.dev/) 95 | web_modules/ 96 | 97 | # TypeScript cache 98 | *.tsbuildinfo 99 | 100 | # Optional npm cache directory 101 | .npm 102 | 103 | # Optional eslint cache 104 | .eslintcache 105 | 106 | # Microbundle cache 107 | .rpt2_cache/ 108 | .rts2_cache_cjs/ 109 | .rts2_cache_es/ 110 | .rts2_cache_umd/ 111 | 112 | # Optional REPL history 113 | .node_repl_history 114 | 115 | # Output of 'npm pack' 116 | *.tgz 117 | 118 | # Yarn Integrity file 119 | .yarn-integrity 120 | 121 | # dotenv environment variables file 122 | .env 123 | .env.test 124 | .env.production 125 | 126 | # parcel-bundler cache (https://parceljs.org/) 127 | .cache 128 | .parcel-cache 129 | 130 | # Next.js build output 131 | .next 132 | out 133 | 134 | # Nuxt.js build / generate output 135 | .nuxt 136 | dist 137 | 138 | # Gatsby files 139 | .cache/ 140 | # Comment in the public line in if your project uses Gatsby and not Next.js 141 | # https://nextjs.org/blog/next-9-1#public-directory-support 142 | # public 143 | 144 | # vuepress build output 145 | .vuepress/dist 146 | 147 | # Serverless directories 148 | .serverless/ 149 | 150 | # FuseBox cache 151 | .fusebox/ 152 | 153 | # DynamoDB Local files 154 | .dynamodb/ 155 | 156 | # TernJS port file 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | .vscode-test 161 | 162 | # yarn v2 163 | .yarn/cache 164 | .yarn/unplugged 165 | .yarn/build-state.yml 166 | .yarn/install-state.gz 167 | .pnp.* 168 | 169 | ### Node Patch ### 170 | # Serverless Webpack directories 171 | .webpack/ 172 | 173 | # Optional stylelint cache 174 | .stylelintcache 175 | 176 | # SvelteKit build / generate output 177 | .svelte-kit 178 | 179 | ### yarn ### 180 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 181 | 182 | .yarn/* 183 | !.yarn/releases 184 | !.yarn/plugins 185 | !.yarn/sdks 186 | !.yarn/versions 187 | 188 | # if you are NOT using Zero-installs, then: 189 | # comment the following lines 190 | !.yarn/cache 191 | 192 | # and uncomment the following lines 193 | # .pnp.* 194 | 195 | # End of https://www.toptal.com/developers/gitignore/api/macos,node,git,yarn 196 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Program", 9 | "type": "node", 10 | "request": "launch", 11 | "skipFiles": ["/**"], 12 | "program": "${workspaceFolder}/src/index.ts", 13 | "preLaunchTask": "tsc: build - tsconfig.json", 14 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 15 | }, 16 | { 17 | "name": "Debug Jest Tests", 18 | "type": "node", 19 | "request": "launch", 20 | "runtimeArgs": [ 21 | "--inspect-brk", 22 | "${workspaceRoot}/node_modules/.bin/jest", 23 | "--runInBand" 24 | ], 25 | "console": "integratedTerminal", 26 | "internalConsoleOptions": "neverOpen", 27 | "port": 9229 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "cSpell.words": ["captchaimg", "catcha", "Interactor", "networkidle"] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "typescript", 6 | "tsconfig": "tsconfig.json", 7 | "problemMatcher": ["$tsc"], 8 | "group": "build", 9 | "label": "tsc: build - tsconfig.json" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021- YeonGyu Kim 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | credit card emoji 3 |

TrackPurchase

4 |

5 | 6 |

7 | 8 | NPM version 9 | 10 | 11 | codecov 12 | 13 | 14 | codecov 15 | 16 |

17 | 18 | > 단 몇줄의 코드로 다양한 쇼핑 플랫폼에서 결제 내역을 긁어오자! 19 | 20 | ## 🛒 지원 플랫폼 목록 21 | 22 | - 지원 플랫폼은 계속해서 추가될 예정입니다. 23 | 24 | [![Naver Pay](https://developer.pay.naver.com/static/img/logo_black.png)](https://pay.naver.com) 25 | 26 | ## 📦 설치 27 | 28 | ```sh 29 | yarn add trackpurchase 30 | ``` 31 | 32 | ## 🚀 사용 33 | 34 | ### 네이버 페이 35 | 36 | ```typescript 37 | import { NaverApp } from "trackpurchase"; 38 | const browser = await puppeteer.launch(); 39 | const page = await browser.newPage(); 40 | 41 | const module = NaverApp.NaverModuleFactory.create(page); 42 | const crawlService = new NaverApp.NaverService(module); 43 | 44 | await crawlService.normalLogin(id, password, 100); 45 | const history = await crawlService.getHistory(); 46 | browser.close(); 47 | 48 | console.log(history); 49 | ``` 50 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-typescript"], 3 | "plugins": ["@babel/plugin-transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: no 4 | patch: no 5 | -------------------------------------------------------------------------------- /docs/images/credit-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-yeongyu/TrackPurchase/9f2ee880f34c5a292bedb6e6747144701e347a22/docs/images/credit-card.png -------------------------------------------------------------------------------- /example/naver/printPaymentHistory.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | import { NaverApp } from "trackpurchase"; 3 | 4 | import readline from "readline"; 5 | 6 | const printNaverPayHistory = async (id: string, password: string) => { 7 | const MOBILE_UA = 8 | "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1"; 9 | 10 | const browser = await puppeteer.launch({ 11 | headless: true, 12 | args: ["--start-maximized"], 13 | }); 14 | const page = await browser.newPage(); 15 | await page.setViewport({ height: 800, width: 1200 }); 16 | await page.setUserAgent(MOBILE_UA); 17 | 18 | const module = NaverApp.NaverModuleFactory.create(page); 19 | const crawlService = new NaverApp.NaverService(module); 20 | 21 | await crawlService.normalLogin(id, password, 100); 22 | 23 | browser.close(); 24 | const history = await crawlService.getHistory(); 25 | console.log(history); 26 | }; 27 | 28 | const rl = readline.createInterface({ 29 | input: process.stdin, 30 | output: process.stdout, 31 | }); 32 | rl.question("Naver ID: ", (id) => { 33 | rl.question("Naver Password: ", (password) => { 34 | printNaverPayHistory(id, password); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trackpurchase", 3 | "version": "2.0.0", 4 | "main": "dist/index.js", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/code-yeongyu/TrackPurchase.git" 9 | }, 10 | "keywords": [ 11 | "crawler", 12 | "web scraper", 13 | "web automation", 14 | "puppeteer" 15 | ], 16 | "description": "단 몇줄의 코드로 다양한 쇼핑 플랫폼에서 결제 내역을 긁어오자!", 17 | "files": [ 18 | "dist" 19 | ], 20 | "jest": { 21 | "preset": "jest-puppeteer", 22 | "coveragePathIgnorePatterns": [ 23 | "dist/", 24 | "elementParser.ts", 25 | "pageInteractor.ts" 26 | ], 27 | "testPathIgnorePatterns": [ 28 | "dist/" 29 | ] 30 | }, 31 | "scripts": { 32 | "test": "jest .", 33 | "test:cov": "jest --coverage --collectCoverageFrom='src/**/*.{js,jsx,ts}'", 34 | "build": "tsc -p ." 35 | }, 36 | "dependencies": { 37 | "axios": "^0.25.0", 38 | "puppeteer": "^12.0.1" 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "^7.16.5", 42 | "@babel/plugin-transform-runtime": "^7.16.5", 43 | "@babel/preset-env": "^7.16.5", 44 | "@babel/preset-typescript": "^7.16.5", 45 | "@babel/runtime": "^7.16.5", 46 | "@types/axios": "^0.14.0", 47 | "@types/expect-puppeteer": "^4.4.7", 48 | "@types/jest": "^27.0.3", 49 | "@types/jest-environment-puppeteer": "^4.4.1", 50 | "babel": "^6.23.0", 51 | "babel-jest": "^27.4.5", 52 | "jest": "^27.4.5", 53 | "jest-puppeteer": "^6.0.3", 54 | "nodemon": "^2.0.15", 55 | "puppeteer-to-istanbul": "^1.4.0", 56 | "ts-node": "^10.4.0", 57 | "tsc": "^2.0.3", 58 | "typescript": "^4.5.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/common/index.ts: -------------------------------------------------------------------------------- 1 | import Module from "./module"; 2 | import PaymentHistory from "./types/paymentHistory"; 3 | 4 | export { Module, PaymentHistory }; 5 | -------------------------------------------------------------------------------- /src/app/common/module.ts: -------------------------------------------------------------------------------- 1 | export default interface Module { 2 | readonly urlChanger: any; 3 | readonly pageInteractor: any; 4 | readonly elementParser: any; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/common/types/paymentHistory.d.ts: -------------------------------------------------------------------------------- 1 | export default interface PaymentHistory { 2 | readonly name: string; 3 | readonly price: number; 4 | readonly thumbnailURL: string; 5 | readonly paymentStatus: string; 6 | readonly isAdditional: boolean; 7 | readonly purchasedAt?: Date; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/common/types/response.d.ts: -------------------------------------------------------------------------------- 1 | export interface CommonResponse { 2 | readonly status: number; 3 | readonly data: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/naver/index.ts: -------------------------------------------------------------------------------- 1 | import { NaverModule } from "./module"; 2 | import { NaverModuleFactory } from "./moduleFactory"; 3 | import { NaverURLChanger } from "./urlChanger"; 4 | import { 5 | LoginEvent, 6 | CaptchaStatus, 7 | NaverPageInteractor, 8 | } from "./pageInteractor"; 9 | import { NaverService } from "./service"; 10 | import { NaverScraper } from "./scraper"; 11 | import { NaverParser } from "./parser"; 12 | 13 | export { 14 | NaverModule, 15 | NaverModuleFactory, 16 | NaverURLChanger, 17 | NaverPageInteractor, 18 | NaverService, 19 | LoginEvent, 20 | CaptchaStatus, 21 | NaverScraper, 22 | NaverParser, 23 | }; 24 | -------------------------------------------------------------------------------- /src/app/naver/module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NaverURLChanger, 3 | NaverPageInteractor, 4 | NaverScraper, 5 | NaverParser, 6 | } from "."; 7 | 8 | export interface NaverModule { 9 | readonly urlChanger: NaverURLChanger; 10 | readonly pageInteractor: NaverPageInteractor; 11 | readonly scraper: NaverScraper; 12 | readonly parser: NaverParser; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/naver/moduleFactory.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | import { 3 | NaverModule, 4 | NaverURLChanger, 5 | NaverPageInteractor, 6 | NaverScraper, 7 | NaverParser, 8 | } from "."; 9 | 10 | export class NaverModuleFactory { 11 | static create(page: puppeteer.Page): NaverModule { 12 | const urlChanger = new NaverURLChanger(page); 13 | const pageInteractor = new NaverPageInteractor(page); 14 | const scraper = new NaverScraper(); 15 | const parser = new NaverParser(); 16 | 17 | return { 18 | urlChanger, 19 | pageInteractor, 20 | scraper, 21 | parser, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/naver/pageInteractor.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | 3 | export type LoginEvent = 4 | | "success" 5 | | "otp-required" 6 | | "manual-otp-required" 7 | | "unexpected"; 8 | 9 | export interface CaptchaStatus { 10 | readonly imageData: string; 11 | readonly question: string; 12 | } 13 | 14 | export class NaverPageInteractor { 15 | constructor(private readonly page: puppeteer.Page) { 16 | this.page = page; 17 | } 18 | 19 | private async clickLoginButton() { 20 | await Promise.all([ 21 | this.page.click("#log\\.login"), 22 | this.page.waitForNavigation({ waitUntil: "networkidle2" }), 23 | ]); 24 | } 25 | private async typeLoginInfo(id: string, password: string, delay: number) { 26 | await this.page.focus("#id"); 27 | await this.page.keyboard.type(id, { delay: delay }); 28 | await this.page.focus("#pw"); 29 | await this.page.keyboard.type(password, { delay: delay }); 30 | await this.clickLoginButton(); 31 | } 32 | private async waitForLoginElements() { 33 | await Promise.all([ 34 | this.page.waitForSelector("#id"), 35 | this.page.waitForSelector("#pw"), 36 | ]); 37 | } 38 | async login(id: string, password: string, delay?: number, loginURL?: string) { 39 | await this.waitForLoginElements(); 40 | await this.typeLoginInfo(id, password, delay || 200); 41 | } 42 | 43 | otpInputSelector = "#otp"; 44 | async getLoginStatus(loginURL?: string): Promise { 45 | const isLoginPage = this.page 46 | .url() 47 | .includes(loginURL || "https://nid.naver.com/nidlogin.login"); 48 | if (!isLoginPage) { 49 | return "success"; 50 | } 51 | 52 | const otpElementDisplayStyle = await this.page.evaluate(() => { 53 | const button = document.querySelector("#remail_btn1"); 54 | if (!(button instanceof HTMLElement)) { 55 | return; 56 | } 57 | return button.style.display; 58 | }); 59 | if (otpElementDisplayStyle !== "") { 60 | return "otp-required"; 61 | } 62 | 63 | const manualOTPElement = await this.page.$(this.otpInputSelector); 64 | if (manualOTPElement) { 65 | return "manual-otp-required"; 66 | } 67 | 68 | return "unexpected"; 69 | } 70 | 71 | captchaImageSelector = "#captchaimg"; 72 | captchaTextSelector = "#captcha_info"; 73 | async getCaptchaStatus(): Promise { 74 | const data = await this.page.evaluate( 75 | (captchaImageSelector: string, captchaTextSelector: string) => { 76 | const captchaImage = document.querySelector( 77 | captchaImageSelector 78 | ) as HTMLElement | null; 79 | const captchaText = document.querySelector( 80 | captchaTextSelector 81 | ) as HTMLElement | null; 82 | 83 | if (!captchaImage || !captchaText) { 84 | return; 85 | } 86 | 87 | const imageData = captchaImage.getAttribute("src") as string; 88 | const question = captchaText.innerText; 89 | 90 | return { imageData, question }; 91 | }, 92 | this.captchaImageSelector, 93 | this.captchaTextSelector 94 | ); 95 | 96 | return data || null; 97 | } 98 | 99 | async fillManualOTPInput(code: string) { 100 | const manualOTPElement = await this.page.$(this.otpInputSelector); 101 | if (!manualOTPElement) { 102 | throw new Error("manual-otp-input-element not found"); 103 | } 104 | await manualOTPElement.type(code); 105 | await manualOTPElement.press("Enter"); 106 | } 107 | 108 | catchaInputSelector = "#captcha"; 109 | async fillCaptchaInput(answer: string, password: string) { 110 | const captchaElement = await this.page.$(this.catchaInputSelector); 111 | if (!captchaElement) { 112 | throw new Error("captcha input element not found"); 113 | } 114 | await captchaElement.type(answer); 115 | 116 | await this.typeLoginInfo("", password, 200); 117 | } 118 | 119 | async getCookies() { 120 | const cookies = await this.page.cookies(); 121 | let cookieString = ""; 122 | for (const cookie of cookies) { 123 | cookieString += `${cookie.name}=${cookie.value}; `; 124 | } 125 | return cookieString; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/app/naver/parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { NaverParser } from "./parser"; 2 | import { 3 | expectedPaymentHistoryItems, 4 | searchPaymentHistoryJson, 5 | } from "./testFixture"; 6 | 7 | describe("NaverParser", () => { 8 | describe("parsePaymentHistory", () => { 9 | it("should parse", () => { 10 | // given 11 | const parser = new NaverParser(); 12 | 13 | // when 14 | const result = parser.parsePaymentHistory(searchPaymentHistoryJson); 15 | 16 | // then 17 | expect(result).toEqual(expectedPaymentHistoryItems); 18 | }); 19 | }); 20 | 21 | describe("parseInformationForNextPaymentHistory", () => { 22 | it("should parse", () => { 23 | // given 24 | const parser = new NaverParser(); 25 | 26 | // when 27 | const result = parser.parseInformationForNextPaymentHistory( 28 | searchPaymentHistoryJson 29 | ); 30 | 31 | // then 32 | expect(result.hasNext).toBe(true); 33 | expect(result.lastHistoryId).toBe("order-2021110243152861"); 34 | expect(result.lastHistoryDateTimestamp).toBe(1635853254000); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/app/naver/parser.ts: -------------------------------------------------------------------------------- 1 | import { PaymentHistory } from "app/common"; 2 | import { PaymentHistoryResponse } from "./types/paymentHistoryResponse"; 3 | 4 | export class NaverParser { 5 | parsePaymentHistory(jsonString: string): PaymentHistory[] { 6 | const data = JSON.parse(jsonString) as PaymentHistoryResponse; 7 | const items = data.result.items; 8 | const paymentHistories = items.map((item): PaymentHistory => { 9 | const name = item.product.name; 10 | const price = item.product.price; 11 | const thumbnailURL = item.product.imgUrl; 12 | const paymentStatus = item.status.text; 13 | const isAdditional = !!item.additionalData.isSupplemented; 14 | const purchasedAtTimestamp = item.date; 15 | const purchasedAt = new Date(purchasedAtTimestamp); 16 | return { 17 | name, 18 | price, 19 | thumbnailURL, 20 | paymentStatus, 21 | isAdditional, 22 | purchasedAt, 23 | }; 24 | }); 25 | return paymentHistories; 26 | } 27 | 28 | parseInformationForNextPaymentHistory(jsonString: string): { 29 | hasNext: boolean; 30 | lastHistoryId: string; 31 | lastHistoryDateTimestamp: number; 32 | } { 33 | const data = JSON.parse(jsonString) as PaymentHistoryResponse; 34 | 35 | const hasNext = data.result.hasNext; 36 | const lastHistoryId = data.result.lastId; 37 | const lastHistoryDateTimestamp = data.result.lastDate; 38 | return { hasNext, lastHistoryId, lastHistoryDateTimestamp }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/naver/scraper.test.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { NaverScraper } from "./scraper"; 3 | describe("Scraper", () => { 4 | describe("searchPaymentHistory", () => { 5 | it("should create an post request", async () => { 6 | // given 7 | const cookies = ""; 8 | const scraper = new NaverScraper(); 9 | const postSpy = jest.spyOn(axios, "post"); 10 | postSpy.mockImplementation(() => Promise.resolve({ data: {} })); 11 | 12 | // when 13 | await scraper.searchPaymentHistory(cookies); 14 | 15 | // then 16 | expect(postSpy).toBeCalledWith( 17 | scraper.searchPaymentHistoryURL, 18 | expect.any(Object), 19 | expect.objectContaining({ 20 | headers: expect.objectContaining({ 21 | Cookie: cookies, 22 | }), 23 | }) 24 | ); 25 | }); 26 | }); 27 | 28 | describe("nextPaymentHistory", () => { 29 | it("should create an post request", async () => { 30 | // given 31 | const second = 1000; 32 | const minute = second * 60; 33 | const hour = minute * 60; 34 | const day = hour * 24; 35 | 36 | const cookies = ""; 37 | const scraper = new NaverScraper(); 38 | const postSpy = jest.spyOn(axios, "post"); 39 | postSpy.mockImplementation(() => Promise.resolve({ data: {} })); 40 | 41 | // when 42 | await scraper.nextPaymentHistory( 43 | cookies, 44 | "order-1234", 45 | new Date().getTime() - day * 30 46 | ); 47 | 48 | // then 49 | expect(postSpy).toBeCalledWith( 50 | scraper.nextPaymentHistoryURL, 51 | expect.any(Object), 52 | expect.objectContaining({ 53 | headers: expect.objectContaining({ 54 | Cookie: cookies, 55 | }), 56 | }) 57 | ); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/app/naver/scraper.ts: -------------------------------------------------------------------------------- 1 | import { CommonResponse } from "app/common/types/response"; 2 | import axios from "axios"; 3 | import { ServiceGroup } from "./types/serviceGroup"; 4 | import { StatusGroup } from "./types/statusGroup"; 5 | 6 | export class NaverScraper { 7 | private async getTodayString() { 8 | const today = new Date(); 9 | const year = today.getFullYear(); 10 | const month = today.getMonth() + 1; 11 | const date = today.getDate(); 12 | const monthString = month >= 10 ? month : "0" + month; 13 | const dateString = date >= 10 ? date : "0" + date; 14 | return `${year}-${monthString}-${dateString}`; 15 | } 16 | 17 | searchPaymentHistoryURL = 18 | "https://new-m.pay.naver.com/api/timeline/searchPaymentHistory"; 19 | async searchPaymentHistory( 20 | cookies: string, 21 | searchOptions?: { 22 | keyword: string; 23 | serviceGroup: ServiceGroup; 24 | statusGroup: StatusGroup; 25 | } 26 | ): Promise { 27 | const data = { 28 | keyword: searchOptions?.keyword || null, 29 | startDate: "2000-01-01", 30 | endDate: await this.getTodayString(), 31 | serviceGroup: searchOptions?.serviceGroup || null, 32 | statusGroup: searchOptions?.statusGroup || null, 33 | }; 34 | const options = { 35 | headers: { 36 | Cookie: cookies, 37 | "content-type": "application/json;charset=UTF-8", 38 | }, 39 | }; 40 | const response = await axios.post( 41 | this.searchPaymentHistoryURL, 42 | data, 43 | options 44 | ); 45 | 46 | return { status: response.status, data: JSON.stringify(response.data) }; 47 | } 48 | 49 | nextPaymentHistoryURL = 50 | "https://new-m.pay.naver.com/api/timeline/nextPaymentHistory"; 51 | async nextPaymentHistory( 52 | cookies: string, 53 | lastHistoryId: string, 54 | lastHistoryDateTimestamp: number, 55 | searchOptions?: { 56 | keyword: string; 57 | serviceGroup: ServiceGroup; 58 | statusGroup: StatusGroup; 59 | } 60 | ): Promise { 61 | const data = { 62 | keyword: searchOptions?.keyword || null, 63 | startDate: "2000-01-01", 64 | endDate: await this.getTodayString(), 65 | serviceGroup: searchOptions?.serviceGroup || null, 66 | statusGroup: searchOptions?.statusGroup || null, 67 | lastId: lastHistoryId, 68 | lastDate: lastHistoryDateTimestamp, 69 | }; 70 | const options = { 71 | headers: { 72 | Cookie: cookies, 73 | "content-type": "application/json;charset=UTF-8", 74 | }, 75 | }; 76 | const response = await axios.post( 77 | this.nextPaymentHistoryURL, 78 | data, 79 | options 80 | ); 81 | return { status: response.status, data: JSON.stringify(response.data) }; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/app/naver/service.ts: -------------------------------------------------------------------------------- 1 | import { PaymentHistory } from "app/common"; 2 | import { CommonResponse } from "app/common/types/response"; 3 | import { NaverModule } from "."; 4 | 5 | export class NaverService { 6 | cookies?: string; 7 | constructor(private readonly module: NaverModule) { 8 | this.module = module; 9 | } 10 | 11 | async normalLogin(id: string, password: string, delay?: number) { 12 | await this.module.urlChanger.moveToLoginURL(); 13 | await this.module.pageInteractor.login(id, password, delay); 14 | this.cookies = await this.module.pageInteractor.getCookies(); 15 | } 16 | 17 | private async isResponseValid(response: CommonResponse) { 18 | return response.status === 200; 19 | } 20 | 21 | private async getHistoryResult(response: CommonResponse) { 22 | if (!this.isResponseValid(response)) { 23 | throw new Error(`Invalid response: ${response.status} ${response.data}`); 24 | } 25 | const historyItems = this.module.parser.parsePaymentHistory(response.data); 26 | const infoForNextPaymentHistory = 27 | this.module.parser.parseInformationForNextPaymentHistory(response.data); 28 | return { historyItems, infoForNextPaymentHistory }; 29 | } 30 | private async getAllPaymentHistories(cookies: string) { 31 | let historyItems: PaymentHistory[]; 32 | let infoForNextPaymentHistory: { 33 | hasNext: boolean; 34 | lastHistoryId: string; 35 | lastHistoryDateTimestamp: number; 36 | }; 37 | 38 | const firstResponse = await this.module.scraper.searchPaymentHistory( 39 | cookies 40 | ); 41 | let firstResult = await this.getHistoryResult(firstResponse); 42 | historyItems = firstResult.historyItems; 43 | infoForNextPaymentHistory = firstResult.infoForNextPaymentHistory; 44 | // get the very first payment history items 45 | 46 | while (infoForNextPaymentHistory.hasNext) { 47 | const response = await this.module.scraper.nextPaymentHistory( 48 | cookies, 49 | infoForNextPaymentHistory.lastHistoryId, 50 | infoForNextPaymentHistory.lastHistoryDateTimestamp 51 | ); 52 | const result = await this.getHistoryResult(response); 53 | historyItems = historyItems.concat(result.historyItems); 54 | infoForNextPaymentHistory = result.infoForNextPaymentHistory; 55 | } 56 | // get payment history continuously until hasNext is false, append it to historyItems 57 | 58 | return historyItems; 59 | } 60 | 61 | async getHistory() { 62 | if (!this.cookies) { 63 | throw new Error("Not logged in"); 64 | } 65 | 66 | const paymentHistories = await this.getAllPaymentHistories(this.cookies); 67 | return paymentHistories; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/app/naver/testFixture.ts: -------------------------------------------------------------------------------- 1 | import { PaymentHistory } from "app/common"; 2 | 3 | export const expectedPaymentHistoryItems: PaymentHistory[] = [ 4 | { 5 | name: "GaN PD3.0 PPS 120W 고속 멀티 충전기 GANMAX130W 노트북 맥북 아이패드 아이폰12 S20 S21 노트20", 6 | price: 60900, 7 | thumbnailURL: 8 | "https://shop-phinf.pstatic.net/20210202_197/1612253340144CRiFt_JPEG/113631_1.jpg", 9 | paymentStatus: "구매확정완료", 10 | isAdditional: false, 11 | purchasedAt: new Date(1643557141000), 12 | }, 13 | { 14 | name: "에어팟 한쪽 단품 2세대 한쪽 정품 왼쪽 오른쪽 에어팟 2세대 본체 낱개 본체만", 15 | price: 64000, 16 | thumbnailURL: 17 | "https://shop-phinf.pstatic.net/20210823_296/1629708714809S9zhq_JPEG/30844542634288263_1229218711.jpg", 18 | paymentStatus: "구매확정완료", 19 | isAdditional: false, 20 | purchasedAt: new Date(1642709308000), 21 | }, 22 | { 23 | name: "쿠키 1개", 24 | price: 100, 25 | thumbnailURL: 26 | "https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png", 27 | paymentStatus: "결제완료", 28 | isAdditional: false, 29 | purchasedAt: new Date(1642602774000), 30 | }, 31 | { 32 | name: "쿠키 1개", 33 | price: 100, 34 | thumbnailURL: 35 | "https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png", 36 | paymentStatus: "결제완료", 37 | isAdditional: false, 38 | purchasedAt: new Date(1642602759000), 39 | }, 40 | { 41 | name: "쿠키 1개", 42 | price: 100, 43 | thumbnailURL: 44 | "https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png", 45 | paymentStatus: "결제완료", 46 | isAdditional: false, 47 | purchasedAt: new Date(1642602479000), 48 | }, 49 | { 50 | name: "네이버플러스 월간 이용권", 51 | price: 4900, 52 | thumbnailURL: 53 | "https://images.bill.naver.net/npay/mylist/20181120/naverplus_170.png", 54 | paymentStatus: "결제완료", 55 | isAdditional: false, 56 | purchasedAt: new Date(1642555264000), 57 | }, 58 | { 59 | name: "쿠키 1개", 60 | price: 100, 61 | thumbnailURL: 62 | "https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png", 63 | paymentStatus: "결제완료", 64 | isAdditional: false, 65 | purchasedAt: new Date(1642436770000), 66 | }, 67 | { 68 | name: "쿠키 1개", 69 | price: 100, 70 | thumbnailURL: 71 | "https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png", 72 | paymentStatus: "결제완료", 73 | isAdditional: false, 74 | purchasedAt: new Date(1642436755000), 75 | }, 76 | { 77 | name: "[하남] 아쿠아필드 찜질스파 하남점 (~2/28)", 78 | price: 19000, 79 | thumbnailURL: 80 | "https://phinf.pstatic.net/checkout/20211231_139/1640926536658m9q1D_JPEG/2003334324241469.jpg", 81 | paymentStatus: "취소완료", 82 | isAdditional: false, 83 | purchasedAt: new Date(1642316328000), 84 | }, 85 | { 86 | name: "네이버플러스 월간 이용권", 87 | price: 4900, 88 | thumbnailURL: 89 | "https://images.bill.naver.net/npay/mylist/20181120/naverplus_170.png", 90 | paymentStatus: "결제완료", 91 | isAdditional: false, 92 | purchasedAt: new Date(1639876781000), 93 | }, 94 | { 95 | name: "쿠키 1개", 96 | price: 100, 97 | thumbnailURL: 98 | "https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png", 99 | paymentStatus: "결제완료", 100 | isAdditional: false, 101 | purchasedAt: new Date(1639750481000), 102 | }, 103 | { 104 | name: "네이버플러스 월간 이용권", 105 | price: 4900, 106 | thumbnailURL: 107 | "https://images.bill.naver.net/npay/mylist/20181120/naverplus_170.png", 108 | paymentStatus: "결제완료", 109 | isAdditional: false, 110 | purchasedAt: new Date(1637285929000), 111 | }, 112 | { 113 | name: "QCY T13APP 무선 블루투스이어폰 ENC 노이즈캔슬", 114 | price: 18500, 115 | thumbnailURL: 116 | "https://shop-phinf.pstatic.net/20210817_177/1629187530001M82JQ_JPEG/30323375722420391_2089469822.jpg", 117 | paymentStatus: "구매확정완료", 118 | isAdditional: false, 119 | purchasedAt: new Date(1637061742000), 120 | }, 121 | { 122 | name: "16색 리모컨 달무드등 조명 집들이 생일 선물 수유 침실 수면 취침 인테리어 크리스마스", 123 | price: 13900, 124 | thumbnailURL: 125 | "https://shop-phinf.pstatic.net/20211022_283/1634879909969QV4UO_JPEG/36015798661642066_1479668143.jpg", 126 | paymentStatus: "구매확정완료", 127 | isAdditional: false, 128 | purchasedAt: new Date(1635853254000), 129 | }, 130 | { 131 | name: "8~13cm(한글&영문12글자이내)/15~24cm(20글자이내)", 132 | price: 2000, 133 | thumbnailURL: 134 | "https://shop-phinf.pstatic.net/20211022_283/1634879909969QV4UO_JPEG/36015798661642066_1479668143.jpg", 135 | paymentStatus: "구매확정완료", 136 | isAdditional: true, 137 | purchasedAt: new Date(1635853254000), 138 | }, 139 | ]; 140 | 141 | export const searchPaymentHistoryJson = ` 142 | {"code":"00","message":"성공","result":{"success":true,"items":[{"_id":"order-2022013188114221","additionalData":{"saleChannelTypeCode":"SHOPN","uniqueKey":"2022013188114221","orderNo":"2022013143395491","productNo":"5375891350","productOrderStatus":"PURCHASE_DECIDED","interlockDeliveryNo":"20220203200886442759","isDeliveryTracking":true,"isSupplemented":false,"orderServiceType":"SMART_STORE","isCafeSafePayment":false,"deliveryMethodTypeCode":"DELIVERY","productOrderCount":1,"orderAmount":60900,"isBranch":false,"isReturnInsurance":false,"claimDeliveryFeeAmount":null,"usePendingYn":false,"deliveryInterlockCompanyCode":"GOODSFLOW","membershipAccumulationAmount":2436,"purchaseAccumulationAmount":1420,"reviewAccumulationAmount":{"textReviewAdmin":50,"textReviewSeller":0,"photoVideoReviewAdmin":150,"photoVideoReviewSeller":0,"afterUseTextReviewAdmin":50,"afterUseTextReviewSeller":0,"afterUsePhotoVideoReviewAdmin":150,"afterUsePhotoVideoReviewSeller":0,"storeCustomerReview":0},"isMembership":true,"isReviewWrite":true,"purchaseDecidedAt":1644242438000},"isHidden":false,"oldTimelineId":"20220131003901CHK2022013188114221","serviceType":"ORDER","merchantNo":"500146510","merchantName":"아임커머스","status":{"name":"PURCHASE_CONFIRMED","text":"구매확정완료","color":"GRAY"},"originalSearchText":"GaN PD3.0 PPS 120W 고속 멀티 충전기 GANMAX130W 노트북 맥북 아이패드 아이폰12 S20 S21 노트20 아임커머스 쇼핑","product":{"name":"GaN PD3.0 PPS 120W 고속 멀티 충전기 GANMAX130W 노트북 맥북 아이패드 아이폰12 S20 S21 노트20","imgUrl":"https://shop-phinf.pstatic.net/20210202_197/1612253340144CRiFt_JPEG/113631_1.jpg","infoUrl":"https://smartstore.naver.com/main/products/5375891350","price":60900,"restAmount":60900,"pointPlus":false,"isGift":false,"myDataName":"GaN PD3.0 PPS 120W 고속 멀티 충전기 GANMAX130W 노트북 맥북 아이패드 아이폰12 S20 S21 노트20"},"buttons":{"getOrderDeliveryDetail":{"text":"주문상세","url":"https://m.pay.naver.com/o/orderStatus/2022013143395491?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"getReceipt":{"text":"영수증조회","url":"https://m.pay.naver.com/o/receipt/hist/2022013143395491"},"repurchase":{"text":"재구매","url":"https://m.pay.naver.com/o/repurchase/2022013188114221?backUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"checkDelivery":{"text":"배송조회","url":"https://m.pay.naver.com/o/orderStatus/deliveryTracking/2022013188114221/ORDER_DELIVERY/api?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"}},"date":1643557141000,"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus/2022013143395491?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory","productDetailUrl":"https://inflow.pay.naver.com/rd?retUrl=https%3A%2F%2Fsmartstore.naver.com%2Fmain%2Fproducts%2F5375891350&no=500146510&tr=ppc&pType=M","message":"","isRemove":true},{"_id":"order-2022012194057831","additionalData":{"saleChannelTypeCode":"SHOPN","uniqueKey":"2022012194057831","orderNo":"2022012161431611","productNo":"5357074459","productOrderStatus":"PURCHASE_DECIDED","interlockDeliveryNo":"20220121200871551386","isDeliveryTracking":true,"isSupplemented":false,"orderServiceType":"SMART_STORE","isCafeSafePayment":false,"deliveryMethodTypeCode":"DELIVERY","productOrderCount":1,"orderAmount":67000,"isBranch":false,"isReturnInsurance":false,"claimDeliveryFeeAmount":null,"usePendingYn":false,"deliveryInterlockCompanyCode":"GOODSFLOW","membershipAccumulationAmount":2560,"purchaseAccumulationAmount":1417,"reviewAccumulationAmount":{"textReviewAdmin":50,"textReviewSeller":0,"photoVideoReviewAdmin":150,"photoVideoReviewSeller":0,"afterUseTextReviewAdmin":50,"afterUseTextReviewSeller":0,"afterUsePhotoVideoReviewAdmin":150,"afterUsePhotoVideoReviewSeller":0,"storeCustomerReview":0},"isMembership":true,"isReviewWrite":true,"purchaseDecidedAt":1643553166000},"isHidden":false,"oldTimelineId":"20220121050828CHK2022012194057831","serviceType":"ORDER","merchantNo":"500259430","merchantName":"디케이스토어","status":{"name":"PURCHASE_CONFIRMED","text":"구매확정완료","color":"GRAY"},"originalSearchText":"에어팟 한쪽 단품 2세대 한쪽 정품 왼쪽 오른쪽 에어팟 2세대 본체 낱개 본체만 디케이스토어 쇼핑","product":{"name":"에어팟 한쪽 단품 2세대 한쪽 정품 왼쪽 오른쪽 에어팟 2세대 본체 낱개 본체만","imgUrl":"https://shop-phinf.pstatic.net/20210823_296/1629708714809S9zhq_JPEG/30844542634288263_1229218711.jpg","infoUrl":"https://smartstore.naver.com/main/products/5357074459","price":64000,"restAmount":64000,"pointPlus":false,"isGift":false,"myDataName":"에어팟 한쪽 단품 2세대 한쪽 정품 왼쪽 오른쪽 에어팟 2세대 본체 낱개 본체만"},"buttons":{"getOrderDeliveryDetail":{"text":"주문상세","url":"https://m.pay.naver.com/o/orderStatus/2022012161431611?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"getReceipt":{"text":"영수증조회","url":"https://m.pay.naver.com/o/receipt/hist/2022012161431611"},"repurchase":{"text":"재구매","url":"https://m.pay.naver.com/o/repurchase/2022012194057831?backUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"checkDelivery":{"text":"배송조회","url":"https://m.pay.naver.com/o/orderStatus/deliveryTracking/2022012194057831/ORDER_DELIVERY/api?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"}},"date":1642709308000,"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus/2022012161431611?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory","productDetailUrl":"https://inflow.pay.naver.com/rd?retUrl=https%3A%2F%2Fsmartstore.naver.com%2Fmain%2Fproducts%2F5357074459&no=500259430&tr=ppc&pType=M","message":"","isRemove":true},{"_id":"contents-12767064313","additionalData":{"uniqueKey":"12767064313","serviceSeq":"10000083"},"date":1642602774000,"isHidden":false,"merchantName":"시리즈","merchantNo":null,"oldTimelineId":"20220119233254BNR12767064313","originalSearchText":"콘텐츠 쿠키 1개 시리즈 네이버 북스","product":{"name":"쿠키 1개","imgUrl":"https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png","infoUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","price":100,"restAmount":100,"pointPlus":false,"isGift":false},"serviceType":"CONTENTS","status":{"name":"PAYMENT_COMPLETED","text":"결제완료","color":"BLACK"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/service/main.nhn?serviceNo=5629"}},"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus","productDetailUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","message":"","isRemove":true},{"_id":"contents-12767062155","additionalData":{"uniqueKey":"12767062155","serviceSeq":"10000083"},"date":1642602759000,"isHidden":false,"merchantName":"시리즈","merchantNo":null,"oldTimelineId":"20220119233239BNR12767062155","originalSearchText":"콘텐츠 쿠키 1개 시리즈 네이버 북스","product":{"name":"쿠키 1개","imgUrl":"https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png","infoUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","price":100,"restAmount":100,"pointPlus":false,"isGift":false},"serviceType":"CONTENTS","status":{"name":"PAYMENT_COMPLETED","text":"결제완료","color":"BLACK"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/service/main.nhn?serviceNo=5629"}},"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus","productDetailUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","message":"","isRemove":true},{"_id":"contents-12767043893","additionalData":{"uniqueKey":"12767043893","serviceSeq":"10000083"},"date":1642602479000,"isHidden":false,"merchantName":"시리즈","merchantNo":null,"oldTimelineId":"20220119232759BNR12767043893","originalSearchText":"콘텐츠 쿠키 1개 시리즈 네이버 북스","product":{"name":"쿠키 1개","imgUrl":"https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png","infoUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","price":100,"restAmount":100,"pointPlus":false,"isGift":false},"serviceType":"CONTENTS","status":{"name":"PAYMENT_COMPLETED","text":"결제완료","color":"BLACK"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/service/main.nhn?serviceNo=5629"}},"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus","productDetailUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","message":"","isRemove":true},{"_id":"contents-12764499442","additionalData":{"uniqueKey":"12764499442","serviceSeq":"10001277"},"date":1642555264000,"isHidden":false,"merchantName":"네이버플러스 멤버십","merchantNo":null,"oldTimelineId":"20220119102104BNR12764499442","originalSearchText":"콘텐츠 네이버플러스 월간 이용권 네이버플러스 멤버십 ","product":{"name":"네이버플러스 월간 이용권","imgUrl":"https://images.bill.naver.net/npay/mylist/20181120/naverplus_170.png","infoUrl":"https://nid.naver.com/membership/my","price":4900,"restAmount":4900,"pointPlus":false,"isGift":false},"serviceType":"CONTENTS","status":{"name":"PAYMENT_COMPLETED","text":"결제완료","color":"BLACK"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/alias/naver_membership/naver_membership_2.naver"}},"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus","productDetailUrl":"https://nid.naver.com/membership/my","message":"","isRemove":true},{"_id":"contents-12760103800","additionalData":{"uniqueKey":"12760103800","serviceSeq":"10000083"},"date":1642436770000,"isHidden":false,"merchantName":"시리즈","merchantNo":null,"oldTimelineId":"20220118012610BNR12760103800","originalSearchText":"콘텐츠 쿠키 1개 시리즈 네이버 북스","product":{"name":"쿠키 1개","imgUrl":"https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png","infoUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","price":100,"restAmount":100,"pointPlus":false,"isGift":false},"serviceType":"CONTENTS","status":{"name":"PAYMENT_COMPLETED","text":"결제완료","color":"BLACK"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/service/main.nhn?serviceNo=5629"}},"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus","productDetailUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","message":"","isRemove":true},{"_id":"contents-12760103392","additionalData":{"uniqueKey":"12760103392","serviceSeq":"10000083"},"date":1642436755000,"isHidden":false,"merchantName":"시리즈","merchantNo":null,"oldTimelineId":"20220118012555BNR12760103392","originalSearchText":"콘텐츠 쿠키 1개 시리즈 네이버 북스","product":{"name":"쿠키 1개","imgUrl":"https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png","infoUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","price":100,"restAmount":100,"pointPlus":false,"isGift":false},"serviceType":"CONTENTS","status":{"name":"PAYMENT_COMPLETED","text":"결제완료","color":"BLACK"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/service/main.nhn?serviceNo=5629"}},"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus","productDetailUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","message":"","isRemove":true},{"_id":"easybooking-2022011614099580","additionalData":{"saleChannelTypeCode":"EASYBOOKING","uniqueKey":"2022011614099580","orderNo":"2022011649237180","productNo":"4241469","productOrderStatus":"CANCELED","interlockDeliveryNo":null,"isDeliveryTracking":false,"isSupplemented":false,"orderServiceType":"CHECKOUT","isCafeSafePayment":false,"deliveryMethodTypeCode":"NOTHING","productOrderCount":1,"orderAmount":19000,"isBranch":false,"isReturnInsurance":false,"claimDeliveryFeeAmount":null,"usePendingYn":false,"claimRequestedAt":1642316414000,"claimRequestReasonType":"INTENT_CHANGED","paymentMethod":"CHECK_CARD","cardCode":"005","membershipAccumulationAmount":190,"purchaseAccumulationAmount":190,"reviewAccumulationAmount":{"textReviewAdmin":0,"textReviewSeller":0,"photoVideoReviewAdmin":0,"photoVideoReviewSeller":0,"afterUseTextReviewAdmin":0,"afterUseTextReviewSeller":0,"afterUsePhotoVideoReviewAdmin":0,"afterUsePhotoVideoReviewSeller":0,"storeCustomerReview":0},"isMembership":true,"claimCompletedAt":1642316414000},"isHidden":false,"oldTimelineId":"20220116155848CHK2022011614099580","serviceType":"BOOKING","merchantNo":"200333432","merchantName":"야놀자레저","status":{"name":"CANCELLED","text":"취소완료","color":"GRAY"},"originalSearchText":"[하남] 아쿠아필드 찜질스파 하남점 (~2/28) 야놀자레저 예약","product":{"name":"[하남] 아쿠아필드 찜질스파 하남점 (~2/28)","imgUrl":"https://phinf.pstatic.net/checkout/20211231_139/1640926536658m9q1D_JPEG/2003334324241469.jpg","infoUrl":"https://booking.naver.com/booking/5/bizes/232359/items/4241469","price":19000,"restAmount":19000,"pointPlus":false,"isGift":false,"myDataName":"[하남] 아쿠아필드 찜질스파 하남점 (~2/28)"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/service/main.help?serviceNo=11713"}},"date":1642316328000,"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus/2022011649237180?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory","productDetailUrl":"https://api.booking.naver.com/v3.1/custom/naver-pay/redirect/2022011614099580/detail","message":"","isRemove":true},{"_id":"contents-12650428630","additionalData":{"uniqueKey":"12650428630","serviceSeq":"10001277"},"date":1639876781000,"isHidden":false,"merchantName":"네이버플러스 멤버십","merchantNo":null,"oldTimelineId":"20211219101941BNR12650428630","originalSearchText":"콘텐츠 네이버플러스 월간 이용권 네이버플러스 멤버십 ","product":{"name":"네이버플러스 월간 이용권","imgUrl":"https://images.bill.naver.net/npay/mylist/20181120/naverplus_170.png","infoUrl":"https://nid.naver.com/membership/my","price":4900,"restAmount":4900,"pointPlus":false,"isGift":false},"serviceType":"CONTENTS","status":{"name":"PAYMENT_COMPLETED","text":"결제완료","color":"BLACK"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/alias/naver_membership/naver_membership_2.naver"}},"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus","productDetailUrl":"https://nid.naver.com/membership/my","message":"","isRemove":true},{"_id":"contents-12645965344","additionalData":{"uniqueKey":"12645965344","serviceSeq":"10000083"},"date":1639750481000,"isHidden":false,"merchantName":"시리즈","merchantNo":null,"oldTimelineId":"20211217231441BNR12645965344","originalSearchText":"콘텐츠 쿠키 1개 시리즈 네이버 북스","product":{"name":"쿠키 1개","imgUrl":"https://phinf.pstatic.net/checkout/20181013_92/water09z_1539417248936w2xT3_PNG/17_s.png","infoUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","price":100,"restAmount":100,"pointPlus":false,"isGift":false},"serviceType":"CONTENTS","status":{"name":"PAYMENT_COMPLETED","text":"결제완료","color":"BLACK"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/service/main.nhn?serviceNo=5629"}},"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus","productDetailUrl":"http://m.nstore.naver.com/my/library/productList.nhn?serviceTypeCode=EBOOK","message":"","isRemove":true},{"_id":"contents-12542601005","additionalData":{"uniqueKey":"12542601005","serviceSeq":"10001277"},"date":1637285929000,"isHidden":false,"merchantName":"네이버플러스 멤버십","merchantNo":null,"oldTimelineId":"20211119103849BNR12542601005","originalSearchText":"콘텐츠 네이버플러스 월간 이용권 네이버플러스 멤버십 ","product":{"name":"네이버플러스 월간 이용권","imgUrl":"https://images.bill.naver.net/npay/mylist/20181120/naverplus_170.png","infoUrl":"https://nid.naver.com/membership/my","price":4900,"restAmount":4900,"pointPlus":false,"isGift":false},"serviceType":"CONTENTS","status":{"name":"PAYMENT_COMPLETED","text":"결제완료","color":"BLACK"},"buttons":{"customerService":{"text":"고객센터","url":"https://m.help.naver.com/support/alias/naver_membership/naver_membership_2.naver"}},"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus","productDetailUrl":"https://nid.naver.com/membership/my","message":"","isRemove":true},{"_id":"order-2021111656427271","additionalData":{"saleChannelTypeCode":"SHOPN","uniqueKey":"2021111656427271","orderNo":"2021111689021241","productNo":"5795304120","productOrderStatus":"PURCHASE_DECIDED","interlockDeliveryNo":"20211117200754849594","isDeliveryTracking":true,"isSupplemented":false,"orderServiceType":"SMART_STORE","isCafeSafePayment":false,"deliveryMethodTypeCode":"DELIVERY","productOrderCount":1,"orderAmount":21000,"isBranch":false,"isReturnInsurance":false,"claimDeliveryFeeAmount":null,"usePendingYn":false,"deliveryInterlockCompanyCode":"SWEETTRACKER","membershipAccumulationAmount":740,"purchaseAccumulationAmount":407,"reviewAccumulationAmount":{"textReviewAdmin":50,"textReviewSeller":0,"photoVideoReviewAdmin":150,"photoVideoReviewSeller":0,"afterUseTextReviewAdmin":50,"afterUseTextReviewSeller":0,"afterUsePhotoVideoReviewAdmin":150,"afterUsePhotoVideoReviewSeller":0,"storeCustomerReview":0},"isMembership":true,"isReviewWrite":true,"isAfterUseReviewWrite":true,"purchaseDecidedAt":1637491356000},"isHidden":false,"oldTimelineId":"20211116202222CHK2021111656427271","serviceType":"ORDER","merchantNo":"500086197","merchantName":"와이엘사이언스","status":{"name":"PURCHASE_CONFIRMED","text":"구매확정완료","color":"GRAY"},"originalSearchText":"QCY T13APP 무선 블루투스이어폰 ENC 노이즈캔슬 와이엘사이언스 쇼핑","product":{"name":"QCY T13APP 무선 블루투스이어폰 ENC 노이즈캔슬","imgUrl":"https://shop-phinf.pstatic.net/20210817_177/1629187530001M82JQ_JPEG/30323375722420391_2089469822.jpg","infoUrl":"https://smartstore.naver.com/main/products/5795304120","price":18500,"restAmount":18500,"pointPlus":false,"isGift":false,"myDataName":"QCY T13APP 무선 블루투스이어폰 ENC 노이즈캔슬"},"buttons":{"getOrderDeliveryDetail":{"text":"주문상세","url":"https://m.pay.naver.com/o/orderStatus/2021111689021241?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"getReceipt":{"text":"영수증조회","url":"https://m.pay.naver.com/o/receipt/hist/2021111689021241"},"repurchase":{"text":"재구매","url":"https://m.pay.naver.com/o/repurchase/2021111656427271?backUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"checkDelivery":{"text":"배송조회","url":"https://m.pay.naver.com/o/orderStatus/deliveryTracking/2021111656427271/ORDER_DELIVERY/api?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"}},"date":1637061742000,"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus/2021111689021241?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory","productDetailUrl":"https://inflow.pay.naver.com/rd?retUrl=https%3A%2F%2Fsmartstore.naver.com%2Fmain%2Fproducts%2F5795304120&no=500086197&tr=ppc&pType=M","message":"","isRemove":true},{"_id":"order-2021110243152851","additionalData":{"saleChannelTypeCode":"SHOPN","uniqueKey":"2021110243152851","orderNo":"2021110279249091","productNo":"3857340329","productOrderStatus":"PURCHASE_DECIDED","interlockDeliveryNo":"20211103200730255097","isDeliveryTracking":true,"isSupplemented":false,"orderServiceType":"SMART_STORE","isCafeSafePayment":false,"deliveryMethodTypeCode":"DELIVERY","productOrderCount":2,"orderAmount":18400,"isBranch":false,"isReturnInsurance":false,"claimDeliveryFeeAmount":null,"usePendingYn":false,"deliveryInterlockCompanyCode":"GOODSFLOW","membershipAccumulationAmount":556,"purchaseAccumulationAmount":415,"reviewAccumulationAmount":{"textReviewAdmin":50,"textReviewSeller":0,"photoVideoReviewAdmin":150,"photoVideoReviewSeller":0,"afterUseTextReviewAdmin":0,"afterUseTextReviewSeller":0,"afterUsePhotoVideoReviewAdmin":0,"afterUsePhotoVideoReviewSeller":0,"storeCustomerReview":0},"isMembership":true,"isReviewWrite":true,"purchaseDecidedAt":1636656010000},"isHidden":false,"oldTimelineId":"20211102204054CHK2021110243152851","serviceType":"ORDER","merchantNo":"510024674","merchantName":"선진","status":{"name":"PURCHASE_CONFIRMED","text":"구매확정완료","color":"GRAY"},"originalSearchText":"16색 리모컨 달무드등 조명 집들이 생일 선물 수유 침실 수면 취침 인테리어 크리스마스 선진 쇼핑","product":{"name":"16색 리모컨 달무드등 조명 집들이 생일 선물 수유 침실 수면 취침 인테리어 크리스마스","imgUrl":"https://shop-phinf.pstatic.net/20211022_283/1634879909969QV4UO_JPEG/36015798661642066_1479668143.jpg","infoUrl":"https://smartstore.naver.com/main/products/3857340329","price":13900,"restAmount":13900,"pointPlus":false,"isGift":false,"myDataName":"16색 리모컨 달무드등 조명 집들이 생일 선물 수유 침실 수면 취침 인테리어 크리스마스"},"buttons":{"getOrderDeliveryDetail":{"text":"주문상세","url":"https://m.pay.naver.com/o/orderStatus/2021110279249091?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"getReceipt":{"text":"영수증조회","url":"https://m.pay.naver.com/o/receipt/hist/2021110279249091"},"repurchase":{"text":"재구매","url":"https://m.pay.naver.com/o/repurchase/2021110243152851?backUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"writeAfterUseReview":{"text":"한달사용리뷰","url":"http://m.shopping.naver.com/reviews/monthly-form?orderNo=2021110279249091&productOrderNos=2021110243152851&returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"checkDelivery":{"text":"배송조회","url":"https://m.pay.naver.com/o/orderStatus/deliveryTracking/2021110243152851/ORDER_DELIVERY/api?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"}},"date":1635853254000,"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus/2021110279249091?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory","productDetailUrl":"https://inflow.pay.naver.com/rd?retUrl=https%3A%2F%2Fsmartstore.naver.com%2Fmain%2Fproducts%2F3857340329&no=510024674&tr=ppc&pType=M","message":"","isRemove":true},{"_id":"order-2021110243152861","additionalData":{"saleChannelTypeCode":"SHOPN","uniqueKey":"2021110243152861","orderNo":"2021110279249091","productNo":"3857340329","productOrderStatus":"PURCHASE_DECIDED","interlockDeliveryNo":"20211103200730255097","isDeliveryTracking":true,"isSupplemented":true,"orderServiceType":"SMART_STORE","isCafeSafePayment":false,"deliveryMethodTypeCode":"DELIVERY","productOrderCount":2,"orderAmount":18400,"isBranch":false,"isReturnInsurance":false,"claimDeliveryFeeAmount":null,"usePendingYn":false,"deliveryInterlockCompanyCode":"GOODSFLOW","membershipAccumulationAmount":0,"purchaseAccumulationAmount":0,"reviewAccumulationAmount":{"textReviewAdmin":0,"textReviewSeller":0,"photoVideoReviewAdmin":0,"photoVideoReviewSeller":0,"afterUseTextReviewAdmin":0,"afterUseTextReviewSeller":0,"afterUsePhotoVideoReviewAdmin":0,"afterUsePhotoVideoReviewSeller":0,"storeCustomerReview":0},"parentProductOrderNo":"2021110243152851","purchaseDecidedAt":1636656011000},"isHidden":false,"oldTimelineId":"20211102204054CHK2021110243152861","serviceType":"ORDER","merchantNo":"510024674","merchantName":"선진","status":{"name":"PURCHASE_CONFIRMED","text":"구매확정완료","color":"GRAY"},"originalSearchText":"8~13cm(한글&영문12글자이내)/15~24cm(20글자이내) 선진 쇼핑","product":{"name":"8~13cm(한글&영문12글자이내)/15~24cm(20글자이내)","imgUrl":"https://shop-phinf.pstatic.net/20211022_283/1634879909969QV4UO_JPEG/36015798661642066_1479668143.jpg","infoUrl":"https://smartstore.naver.com/main/products/3857340329","price":2000,"restAmount":2000,"pointPlus":false,"isGift":false,"myDataName":"16색 리모컨 달무드등 조명 집들이 생일 선물 수유 침실 수면 취침 인테리어 크리스마스"},"buttons":{"getOrderDeliveryDetail":{"text":"주문상세","url":"https://m.pay.naver.com/o/orderStatus/2021110279249091?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"getReceipt":{"text":"영수증조회","url":"https://m.pay.naver.com/o/receipt/hist/2021110279249091"},"repurchase":{"text":"재구매","url":"https://m.pay.naver.com/o/repurchase/2021110243152861?backUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"},"checkDelivery":{"text":"배송조회","url":"https://m.pay.naver.com/o/orderStatus/deliveryTracking/2021110243152861/ORDER_DELIVERY/api?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory"}},"date":1635853254000,"orderDetailUrl":"https://m.pay.naver.com/o/orderStatus/2021110279249091?returnUrl=https%3A%2F%2Fnew-m.pay.naver.com%2F%23%2Fhistorybenefit%2Fpaymenthistory","productDetailUrl":"https://inflow.pay.naver.com/rd?retUrl=https%3A%2F%2Fsmartstore.naver.com%2Fmain%2Fproducts%2F3857340329&no=510024674&tr=ppc&pType=M","message":"","isRemove":true}],"hasNext":true,"lastDate":1635853254000,"lastId":"order-2021110243152861","keywords":[]}} 143 | `; 144 | -------------------------------------------------------------------------------- /src/app/naver/types/paymentHistoryResponse.d.ts: -------------------------------------------------------------------------------- 1 | export interface PaymentHistoryResponse { 2 | code: string; 3 | message: string; 4 | result: Result; 5 | } 6 | 7 | export interface Result { 8 | success: boolean; 9 | items: Item[]; 10 | hasNext: boolean; 11 | lastDate: number; 12 | lastId: string; 13 | } 14 | 15 | export interface Item { 16 | _id: string; 17 | additionalData: AdditionalData; 18 | isHidden: boolean; 19 | oldTimelineId: string; 20 | serviceType: string; 21 | merchantNo: string; 22 | merchantName: string; 23 | status: Status; 24 | originalSearchText: string; 25 | product: Product; 26 | buttons: Buttons; 27 | date: number; 28 | orderDetailUrl: string; 29 | productDetailUrl: string; 30 | message: string; 31 | y; 32 | isRemove: boolean; 33 | deliveryProgress: DeliveryProgress; 34 | } 35 | 36 | export interface ReviewAccumulationAmount { 37 | textReviewAdmin: number; 38 | textReviewSeller: number; 39 | photoVideoReviewAdmin: number; 40 | photoVideoReviewSeller: number; 41 | afterUseTextReviewAdmin: number; 42 | afterUseTextReviewSeller: number; 43 | afterUsePhotoVideoReviewAdmin: number; 44 | afterUsePhotoVideoReviewSeller: number; 45 | storeCustomerReview: number; 46 | } 47 | 48 | export interface AdditionalData { 49 | saleChannelTypeCode: string; 50 | uniqueKey: string; 51 | orderNo: string; 52 | productNo: string; 53 | productOrderStatus: string; 54 | interlockDeliveryNo: string; 55 | isDeliveryTracking: boolean; 56 | isSupplemented: boolean; 57 | orderServiceType: string; 58 | isCafeSafePayment: boolean; 59 | deliveryMethodTypeCode: string; 60 | productOrderCount: number; 61 | orderAmount: number; 62 | isBranch: boolean; 63 | isReturnInsurance: boolean; 64 | usePendingYn: boolean; 65 | deliveryInterlockCompanyCode: string; 66 | membershipAccumulationAmount: number; 67 | purchaseAccumulationAmount: number; 68 | reviewAccumulationAmount: ReviewAccumulationAmount; 69 | isMembership: boolean; 70 | isReviewWrite?: boolean; 71 | purchaseDecidedAt?: number; 72 | serviceSeq: string; 73 | claimRequestedAt?: number; 74 | claimRequestReasonType: string; 75 | paymentMethod: string; 76 | cardCode: string; 77 | claimCompletedAt?: number; 78 | isAfterUseReviewWrite?: boolean; 79 | parentProductOrderNo: string; 80 | } 81 | 82 | export interface Status { 83 | name: StatusGroup; 84 | text: StatusGroupText; 85 | color: string; 86 | } 87 | 88 | export interface Product { 89 | name: string; 90 | imgUrl: string; 91 | infoUrl: string; 92 | price: number; 93 | restAmount: number; 94 | pointPlus: boolean; 95 | isGift: boolean; 96 | myDataName: string; 97 | } 98 | 99 | export interface GetOrderDeliveryDetail { 100 | text: string; 101 | url: string; 102 | } 103 | 104 | export interface GetReceipt { 105 | text: string; 106 | url: string; 107 | } 108 | 109 | export interface RequestRefund { 110 | text: string; 111 | url: string; 112 | } 113 | 114 | export interface RequestExchange { 115 | text: string; 116 | url: string; 117 | } 118 | 119 | export interface CheckDelivery { 120 | text: string; 121 | url: string; 122 | } 123 | 124 | export interface ConfirmPurchase { 125 | name: string; 126 | text: string; 127 | url: string; 128 | purchaseAccumulationAmount: number; 129 | } 130 | 131 | export interface Repurchase { 132 | text: string; 133 | url: string; 134 | } 135 | 136 | export interface CustomerService { 137 | text: string; 138 | url: string; 139 | } 140 | 141 | export interface WriteAfterUseReview { 142 | text: string; 143 | url: string; 144 | } 145 | 146 | export interface Buttons { 147 | getOrderDeliveryDetail: GetOrderDeliveryDetail; 148 | getReceipt: GetReceipt; 149 | requestRefund: RequestRefund; 150 | requestExchange: RequestExchange; 151 | checkDelivery: CheckDelivery; 152 | confirmPurchase: ConfirmPurchase; 153 | repurchase: Repurchase; 154 | customerService: CustomerService; 155 | writeAfterUseReview: WriteAfterUseReview; 156 | } 157 | 158 | export interface ExposureTypeList { 159 | step: number; 160 | stepName: string; 161 | statusType: string; 162 | title: string; 163 | description: string; 164 | needDateInfo: boolean; 165 | needPlaceInfo: boolean; 166 | } 167 | 168 | export interface DeliveryProgress { 169 | currentExposureType: number; 170 | exposureTypeList: ExposureTypeList[]; 171 | } 172 | -------------------------------------------------------------------------------- /src/app/naver/types/serviceGroup.ts: -------------------------------------------------------------------------------- 1 | export enum ServiceGroup { 2 | SHOPPING = "SHOPPING", 3 | BOOKING = "BOOKING", 4 | QR_PAYMENT = "QR_PAYMENT", 5 | CONTENTS = "CONTENTS", 6 | SUBSCRIPTION = "SUBSCRIPTION", 7 | TAX = "TAX", 8 | } 9 | -------------------------------------------------------------------------------- /src/app/naver/types/statusGroup.ts: -------------------------------------------------------------------------------- 1 | export enum StatusGroup { 2 | PAYMENT_WAITING = "PAYMENT_WAITING", 3 | PAYMENT_COMPLETED = "PAYMENT_COMPLETED", 4 | DELIVERY_PREPARING_OR_DELIVERING = "DELIVERY_PREPARING_OR_DELIVERING", 5 | DELIVERED = "DELIVERED", 6 | PURCHASE_CONFIRMED = "PURCHASE_CONFIRMED", 7 | CANCEL = "CANCEL", 8 | REFUND = "REFUND", 9 | EXCHANGE = "EXCHANGE", 10 | } 11 | 12 | export enum StatusGroupText { 13 | ALL = "전체", 14 | PAYMENT_WAITING = "결제대기중", 15 | PAYMENT_COMPLETED = "결제완료", 16 | DELIVERY_PREPARING_OR_DELIVERING = "배송준비중/배송중", 17 | DELIVERED = "배송완료", 18 | PURCHASE_CONFIRMED = "구매확정", 19 | CANCEL = "취소", 20 | REFUND = "반품", 21 | EXCHANGE = "교환", 22 | } 23 | -------------------------------------------------------------------------------- /src/app/naver/urlChanger.test.ts: -------------------------------------------------------------------------------- 1 | import { NaverURLChanger } from "./urlChanger"; 2 | 3 | describe("URLChanger", () => { 4 | describe("moveToLoginURL", () => { 5 | it("Should move page to login", async () => { 6 | // given 7 | const urlChanger = new NaverURLChanger(page); 8 | const pageSpy = jest.spyOn(page, "goto"); 9 | 10 | // when 11 | await urlChanger.moveToLoginURL(); 12 | 13 | // then 14 | expect(pageSpy).toHaveBeenCalledWith(urlChanger.loginURL); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/app/naver/urlChanger.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | 3 | export class NaverURLChanger { 4 | constructor(private readonly page: puppeteer.Page) { 5 | this.page = page; 6 | } 7 | 8 | loginURL = "https://nid.naver.com/nidlogin.login"; 9 | async moveToLoginURL() { 10 | await this.page.goto(this.loginURL); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * as NaverApp from "./app/naver"; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "commonjs", 5 | "strict": true, 6 | "moduleResolution": "node", 7 | "outDir": "dist", 8 | "baseUrl": "src", 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "declaration": true, 13 | "sourceMap": true, 14 | "types": ["puppeteer", "expect-puppeteer", "jest-environment-puppeteer"] 15 | }, 16 | "include": ["src/**/*"] 17 | } 18 | --------------------------------------------------------------------------------