├── .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 |
3 |
TrackPurchase
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | > 단 몇줄의 코드로 다양한 쇼핑 플랫폼에서 결제 내역을 긁어오자!
19 |
20 | ## 🛒 지원 플랫폼 목록
21 |
22 | - 지원 플랫폼은 계속해서 추가될 예정입니다.
23 |
24 | [](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 |
--------------------------------------------------------------------------------