Skip to content

Commit 6a257fd

Browse files
committed
[Feat] 쿠팡 장바구니 담기 + 결제 구현
1 parent 3c001c9 commit 6a257fd

23 files changed

+2806
-10
lines changed

Diff for: .gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,8 @@ yarn-error.log*
3636
next-env.d.ts
3737

3838
*storybook.log
39+
node_modules/
40+
/test-results/
41+
/playwright-report/
42+
/blob-report/
43+
/playwright/.cache/

Diff for: actions/11street/11streetActions.ts

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
'use server';
2+
3+
import SessionBrowserManager from '@/hooks/sessionBrowserManager';
4+
5+
const BASE_URL = 'https://www.11st.co.kr/main';
6+
const DETAIL_BASE_URL = 'https://www.coupang.com/vp/products/';
7+
8+
function delay(ms: number): Promise<void> {
9+
return new Promise((resolve) => setTimeout(resolve, ms));
10+
}
11+
12+
export const coupangSignIn = async () => {
13+
const { page, status } = await SessionBrowserManager.getInstance();
14+
15+
if (status !== 'NOT_SIGNIN') {
16+
return;
17+
}
18+
19+
await page.goto(BASE_URL);
20+
await delay(Math.random() * 4000 + 2000);
21+
22+
await page.locator('.login').click();
23+
await delay(Math.random() * 4000 + 2000);
24+
25+
const coupangId = process.env.COUPANG_ID;
26+
const coupangPw = process.env.COUPANG_PW;
27+
await page.locator('._loginIdInput').fill(coupangId ?? '');
28+
await delay(Math.random() * 4000 + 2000);
29+
30+
await page.locator('._loginPasswordInput').fill(coupangPw ?? '');
31+
await delay(Math.random() * 3000 + 4000);
32+
33+
await page.locator('.login__button--submit').click();
34+
await delay(Math.random() * 3000 + 4000);
35+
};
36+
37+
export const coupangGetPincode = async () => {
38+
const sessionBrowserManager = await SessionBrowserManager.getInstance();
39+
const { page, status } = sessionBrowserManager;
40+
41+
if (status !== 'NOT_SIGNIN') {
42+
return false;
43+
}
44+
45+
const isPresent =
46+
(await page.locator('.pincode-content__button').count()) > 0;
47+
48+
if (!isPresent) {
49+
return false;
50+
}
51+
52+
await page.locator('.pincode-content__button').click();
53+
await delay(Math.random() * 4000 + 2000);
54+
55+
if (isPresent) {
56+
sessionBrowserManager.status = 'PINCODE';
57+
return true;
58+
}
59+
};
60+
61+
export const coupangSetPincode = async (pincode: string) => {
62+
console.log('🚀 ~ coupangSetPincode ~ pincode:', pincode);
63+
const sessionBrowserManager = await SessionBrowserManager.getInstance();
64+
const { page, status } = sessionBrowserManager;
65+
66+
if (status !== 'PINCODE') {
67+
if (status === 'NOT_SIGNIN') {
68+
sessionBrowserManager.status = 'SIGNIN';
69+
}
70+
return;
71+
}
72+
73+
await page
74+
.locator('.pincode-input__pincode-input-box__pincode')
75+
.fill(pincode);
76+
await delay(Math.random() * 4000 + 2000);
77+
78+
await page.locator('.pincode-input__button').click();
79+
await delay(Math.random() * 4000 + 2000);
80+
sessionBrowserManager.status = 'SIGNIN';
81+
};
82+
83+
export const coupangAddCart = async ({
84+
pid,
85+
quantity,
86+
}: {
87+
pid: string;
88+
quantity: number;
89+
}) => {
90+
const { page, status } = await SessionBrowserManager.getInstance();
91+
92+
if (status !== 'SIGNIN') {
93+
return;
94+
}
95+
console.log('Add Cart Function!! ~ ', status);
96+
await page.goto(DETAIL_BASE_URL + pid);
97+
await delay(Math.random() * 4000 + 2000);
98+
99+
await page.locator('.prod-quantity__input').fill(quantity.toString());
100+
await delay(Math.random() * 4000 + 2000);
101+
102+
await page.locator('.prod-cart-btn').click();
103+
await delay(Math.random() * 5000 + 3000);
104+
};
105+
106+
export const coupangPayAll = async () => {
107+
const sessionBrowserManager = await SessionBrowserManager.getInstance();
108+
const { page, status } = sessionBrowserManager;
109+
110+
if (status !== 'SIGNIN') {
111+
return;
112+
}
113+
console.log('Pay All Function!!');
114+
115+
await page.goto(BASE_URL);
116+
await delay(Math.random() * 4000 + 2000);
117+
await page.locator('.mycart-preview-module').click();
118+
await delay(Math.random() * 4000 + 2000);
119+
await page.locator('.order-buttons').click();
120+
await delay(Math.random() * 4000 + 2000);
121+
await page.locator('.paymentBtn-v2-style').click();
122+
await delay(Math.random() * 4000 + 2000);
123+
124+
await delay(Math.random() * 6000 + 2000);
125+
const screenshotBuffer = await page
126+
.locator('#modal-callLGPayment')
127+
.screenshot();
128+
await delay(Math.random() * 4000 + 2000);
129+
130+
sessionBrowserManager.status = 'PAYMENT';
131+
return screenshotBuffer;
132+
};
133+
134+
export const coupangInsertPassword = async (password: string) => {
135+
console.log('🚀 ~ coupangPayment');
136+
137+
const sessionBrowserManager = await SessionBrowserManager.getInstance();
138+
const { page, status } = sessionBrowserManager;
139+
140+
if (status != 'PAYMENT') {
141+
return;
142+
}
143+
144+
const iframe = page.frameLocator('#callLGPayment');
145+
for (const numpad of password) {
146+
await iframe.locator(`[data-key="${numpad}"]`).click();
147+
await delay(Math.random() * 1000 + 2000);
148+
}
149+
};

Diff for: actions/coupang/coupangActions.ts

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
'use server';
2+
3+
import { Item } from '@/app/api/coupang/route';
4+
import SessionBrowserManager from '@/hooks/sessionBrowserManager';
5+
6+
const BASE_URL = 'https://www.coupang.com/';
7+
const DETAIL_BASE_URL = 'https://www.coupang.com/vp/products/';
8+
9+
const DELAY = 2000;
10+
const RANGE = 2000;
11+
12+
function delay(ms: number): Promise<void> {
13+
return new Promise((resolve) => setTimeout(resolve, ms));
14+
}
15+
16+
export const coupangSignIn = async () => {
17+
const { page, status } = await SessionBrowserManager.getInstance();
18+
19+
if (status !== 'NOT_SIGNIN') {
20+
return;
21+
}
22+
23+
await page.goto(BASE_URL);
24+
console.log('Go to Main');
25+
await delay(Math.random() * DELAY + RANGE);
26+
27+
await page.locator('.login').click();
28+
console.log('Login Button Clicked!');
29+
await delay(Math.random() * DELAY + RANGE);
30+
31+
const coupangId = process.env.COUPANG_ID;
32+
const coupangPw = process.env.COUPANG_PW;
33+
await page.locator('._loginIdInput').fill(coupangId ?? '');
34+
console.log('Login ID Filled!');
35+
await delay(Math.random() * DELAY + RANGE);
36+
37+
await page.locator('._loginPasswordInput').fill(coupangPw ?? '');
38+
console.log('Login PWD Filled!');
39+
await delay(Math.random() * DELAY + RANGE);
40+
41+
await page.locator('.login__button--submit').click();
42+
console.log('Login Button Clicked!');
43+
await delay(Math.random() * DELAY + RANGE);
44+
};
45+
46+
export const coupangGetPincode = async () => {
47+
const sessionBrowserManager = await SessionBrowserManager.getInstance();
48+
const { page, status } = sessionBrowserManager;
49+
50+
if (status !== 'NOT_SIGNIN') {
51+
return false;
52+
}
53+
54+
const isPresent =
55+
(await page.locator('.pincode-content__button').count()) > 0;
56+
57+
if (!isPresent) {
58+
return false;
59+
}
60+
61+
await page.locator('.pincode-content__button').click();
62+
await delay(Math.random() * DELAY + RANGE);
63+
64+
if (isPresent) {
65+
sessionBrowserManager.status = 'PINCODE';
66+
return true;
67+
}
68+
};
69+
70+
export const coupangSetPincode = async (pincode: string) => {
71+
console.log('🚀 ~ coupangSetPincode ~ pincode:', pincode);
72+
const sessionBrowserManager = await SessionBrowserManager.getInstance();
73+
const { page, status } = sessionBrowserManager;
74+
75+
if (status !== 'PINCODE') {
76+
if (status === 'NOT_SIGNIN') {
77+
sessionBrowserManager.status = 'SIGNIN';
78+
}
79+
return;
80+
}
81+
82+
await page
83+
.locator('.pincode-input__pincode-input-box__pincode')
84+
.fill(pincode);
85+
await delay(Math.random() * DELAY + RANGE);
86+
87+
await page.locator('.pincode-input__button').click();
88+
await delay(Math.random() * DELAY + RANGE);
89+
sessionBrowserManager.status = 'SIGNIN';
90+
};
91+
92+
export const coupangAddCart = async ({ productId, itemId, quantity }: Item) => {
93+
const { page, status } = await SessionBrowserManager.getInstance();
94+
95+
if (status !== 'SIGNIN') {
96+
return;
97+
}
98+
console.log('Add Cart Function!! ~ ', status);
99+
const url = itemId
100+
? DETAIL_BASE_URL + productId + '?vendorItemId=' + itemId
101+
: DETAIL_BASE_URL + productId;
102+
await page.goto(url);
103+
await delay(Math.random() * DELAY + RANGE);
104+
105+
await page.locator('.prod-quantity__input').fill(quantity.toString());
106+
console.log('Product Quantity Set');
107+
await delay(Math.random() * DELAY + RANGE);
108+
109+
await page.locator('.prod-cart-btn').click();
110+
console.log('Product Cart Button Clicked!');
111+
await delay(Math.random() * DELAY + RANGE);
112+
};
113+
114+
export const coupangPayAll = async () => {
115+
const sessionBrowserManager = await SessionBrowserManager.getInstance();
116+
const { page, status } = sessionBrowserManager;
117+
118+
if (status !== 'SIGNIN') {
119+
return;
120+
}
121+
console.log('Pay All Function!!');
122+
123+
await page.goto(BASE_URL);
124+
await delay(Math.random() * DELAY + RANGE);
125+
126+
await page.locator('.mycart-preview-module').click();
127+
console.log('MyCart Button Clicked!');
128+
await delay(Math.random() * DELAY + RANGE);
129+
130+
await page.locator('.order-buttons').click();
131+
console.log('Order Button Clicked!');
132+
await delay(Math.random() * DELAY + RANGE);
133+
134+
await page.locator('.paymentBtn-v2-style').click();
135+
console.log('Payment Button Clicked!');
136+
await delay(Math.random() * DELAY + RANGE);
137+
138+
await delay(Math.random() * (DELAY + 2000) + RANGE);
139+
const screenshotBuffer = await page
140+
.locator('#modal-callLGPayment')
141+
.screenshot();
142+
143+
sessionBrowserManager.status = 'PAYMENT';
144+
return screenshotBuffer;
145+
};
146+
147+
export const coupangInsertPassword = async (password: string) => {
148+
const sessionBrowserManager = await SessionBrowserManager.getInstance();
149+
const { page, status } = sessionBrowserManager;
150+
151+
console.log('🚀 ~ coupangPayment ~ ', status);
152+
153+
if (status !== 'PAYMENT') {
154+
return;
155+
}
156+
157+
const iframe = page.frameLocator('#callLGPayment');
158+
for (const numpad of password) {
159+
await iframe.locator(`[data-key="${numpad}"]`).click();
160+
console.log('numpad ', numpad, 'clicked!');
161+
await delay(Math.random() * 1000 + RANGE);
162+
}
163+
return;
164+
};
165+
166+
export const coupangClose = async () => {
167+
await SessionBrowserManager.close();
168+
};

0 commit comments

Comments
 (0)