ppaynode 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/module.js +418 -0
- package/package.json +19 -0
package/module.js
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const { v4: uuidv4 } = require('uuid');
|
|
3
|
+
|
|
4
|
+
// --- Custom Error Classes ---
|
|
5
|
+
class PayPayError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(typeof message === 'object' ? JSON.stringify(message) : message);
|
|
8
|
+
this.name = "PayPayError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class PayPayNetWorkError extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(typeof message === 'object' ? JSON.stringify(message) : message);
|
|
15
|
+
this.name = "PayPayNetWorkError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class PayPayLoginError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(typeof message === 'object' ? JSON.stringify(message) : message);
|
|
22
|
+
this.name = "PayPayLoginError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// --- Constants ---
|
|
27
|
+
const HEADERS = {
|
|
28
|
+
"Accept": "application/json, text/plain, */*",
|
|
29
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
|
|
30
|
+
"Content-Type": "application/json"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// --- Helper for Date (JST ISO Format) ---
|
|
34
|
+
function getJstTime() {
|
|
35
|
+
// 現在時刻(UTC)を取得し、+9時間してJSTにする
|
|
36
|
+
const now = new Date();
|
|
37
|
+
const jstOffset = 9 * 60 * 60 * 1000;
|
|
38
|
+
const jstDate = new Date(now.getTime() + jstOffset);
|
|
39
|
+
|
|
40
|
+
// YYYY-MM-DDTHH:mm:ss形式にし、+0900をつける
|
|
41
|
+
// toISOString() は末尾にZがつくが、時刻はずらしてあるのでZをとって+0900にする
|
|
42
|
+
return jstDate.toISOString().replace(/\.\d{3}Z$/, '+0900');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class PayPay {
|
|
46
|
+
/**
|
|
47
|
+
* @param {Object} options
|
|
48
|
+
* @param {string} [options.phone]
|
|
49
|
+
* @param {string} [options.password]
|
|
50
|
+
* @param {string} [options.client_uuid]
|
|
51
|
+
* @param {string} [options.access_token]
|
|
52
|
+
* @param {Object} [options.proxy] - axios proxy config { host, port, auth }
|
|
53
|
+
*/
|
|
54
|
+
constructor({ phone = null, password = null, client_uuid = uuidv4().toUpperCase(), access_token = null, proxy = null } = {}) {
|
|
55
|
+
this.client_uuid = client_uuid;
|
|
56
|
+
this.phone = phone;
|
|
57
|
+
this.password = password;
|
|
58
|
+
this.proxy = proxy;
|
|
59
|
+
this.access_token = access_token;
|
|
60
|
+
this.otp_prefix = null;
|
|
61
|
+
this.otp_reference_id = null;
|
|
62
|
+
|
|
63
|
+
// Axiosインスタンスの作成
|
|
64
|
+
this.session = axios.create({
|
|
65
|
+
headers: HEADERS,
|
|
66
|
+
proxy: this.proxy,
|
|
67
|
+
validateStatus: () => true // エラーコードでもthrowさせず自分でハンドリングする
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 初期トークンがあればセット
|
|
71
|
+
if (this.access_token) {
|
|
72
|
+
this.setToken(this.access_token);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// トークンをヘッダー(Cookie)にセットするヘルパー
|
|
77
|
+
setToken(token) {
|
|
78
|
+
this.access_token = token;
|
|
79
|
+
// Cookieヘッダーとしてセット
|
|
80
|
+
this.session.defaults.headers.common['Cookie'] = `token=${token}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Pythonの__init__内のログインロジックに相当。
|
|
85
|
+
* Node.jsのconstructorはasyncにできないため、別途呼び出す必要があります。
|
|
86
|
+
* access_tokenがない場合に実行してください。
|
|
87
|
+
*/
|
|
88
|
+
async authenticate() {
|
|
89
|
+
if (this.access_token) return;
|
|
90
|
+
|
|
91
|
+
if (this.phone) {
|
|
92
|
+
const payload = {
|
|
93
|
+
"scope": "SIGN_IN",
|
|
94
|
+
"client_uuid": this.client_uuid,
|
|
95
|
+
"grant_type": "password",
|
|
96
|
+
"username": this.phone,
|
|
97
|
+
"password": this.password,
|
|
98
|
+
"add_otp_prefix": true,
|
|
99
|
+
"language": "ja"
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const response = await this.session.post("https://www.paypay.ne.jp/app/v1/oauth/token", payload);
|
|
104
|
+
const data = response.data;
|
|
105
|
+
|
|
106
|
+
if (data.access_token) {
|
|
107
|
+
this.setToken(data.access_token);
|
|
108
|
+
} else if (data.response_type === "ErrorResponse") {
|
|
109
|
+
throw new PayPayLoginError(data);
|
|
110
|
+
} else {
|
|
111
|
+
// OTPが必要な場合
|
|
112
|
+
this.otp_prefix = data.otp_prefix;
|
|
113
|
+
this.otp_reference_id = data.otp_reference_id;
|
|
114
|
+
return { status: "OTP_REQUIRED", ...data };
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof PayPayLoginError) throw error;
|
|
118
|
+
// Axiosのエラーやネットワークエラー
|
|
119
|
+
throw new PayPayNetWorkError(error.message || error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async login(otp) {
|
|
125
|
+
const payload = {
|
|
126
|
+
"scope": "SIGN_IN",
|
|
127
|
+
"client_uuid": this.client_uuid,
|
|
128
|
+
"grant_type": "otp",
|
|
129
|
+
"otp_prefix": this.otp_prefix,
|
|
130
|
+
"otp": otp,
|
|
131
|
+
"otp_reference_id": this.otp_reference_id,
|
|
132
|
+
"username_type": "MOBILE",
|
|
133
|
+
"language": "ja"
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const response = await this.session.post("https://www.paypay.ne.jp/app/v1/oauth/token", payload);
|
|
137
|
+
const data = response.data;
|
|
138
|
+
|
|
139
|
+
if (data.access_token) {
|
|
140
|
+
this.setToken(data.access_token);
|
|
141
|
+
} else {
|
|
142
|
+
throw new PayPayLoginError(data);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async resend_otp(otp_reference_id) {
|
|
149
|
+
const payload = { "add_otp_prefix": "true" };
|
|
150
|
+
const url = `https://www.paypay.ne.jp/app/v1/otp/mobile/resend/${otp_reference_id}`;
|
|
151
|
+
|
|
152
|
+
const response = await this.session.post(url, payload);
|
|
153
|
+
const data = response.data;
|
|
154
|
+
|
|
155
|
+
if (data.otp_prefix && data.otp_reference_id) {
|
|
156
|
+
this.otp_prefix = data.otp_prefix;
|
|
157
|
+
this.otp_reference_id = data.otp_reference_id;
|
|
158
|
+
} else {
|
|
159
|
+
throw new PayPayLoginError(data);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return data;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async get_balance() {
|
|
166
|
+
if (!this.access_token) throw new PayPayLoginError("まずはログインしてください");
|
|
167
|
+
|
|
168
|
+
const response = await this.session.get("https://www.paypay.ne.jp/app/v1/bff/getBalanceInfo");
|
|
169
|
+
const balance = response.data;
|
|
170
|
+
|
|
171
|
+
if (balance.header.resultCode === "S0001") throw new PayPayLoginError(balance);
|
|
172
|
+
if (balance.header.resultCode !== "S0000") throw new PayPayError(balance);
|
|
173
|
+
|
|
174
|
+
let money;
|
|
175
|
+
try {
|
|
176
|
+
money = balance.payload.walletDetail.emoneyBalanceInfo.balance;
|
|
177
|
+
} catch (e) {
|
|
178
|
+
money = null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const money_light = balance.payload.walletDetail.prepaidBalanceInfo.balance;
|
|
182
|
+
const all_balance = balance.payload.walletSummary.allTotalBalanceInfo.balance;
|
|
183
|
+
const useable_balance = balance.payload.walletSummary.usableBalanceInfoWithoutCashback.balance;
|
|
184
|
+
const points = balance.payload.walletDetail.cashBackBalanceInfo.balance;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
money,
|
|
188
|
+
money_light,
|
|
189
|
+
all_balance,
|
|
190
|
+
useable_balance,
|
|
191
|
+
points,
|
|
192
|
+
raw: balance
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async get_profile() {
|
|
197
|
+
if (!this.access_token) throw new PayPayLoginError("まずはログインしてください");
|
|
198
|
+
|
|
199
|
+
const response = await this.session.get("https://www.paypay.ne.jp/app/v1/getUserProfile");
|
|
200
|
+
const profile = response.data;
|
|
201
|
+
|
|
202
|
+
if (profile.header.resultCode === "S0001") throw new PayPayLoginError(profile);
|
|
203
|
+
if (profile.header.resultCode !== "S0000") throw new PayPayError(profile);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
name: profile.payload.userProfile.nickName,
|
|
207
|
+
external_user_id: profile.payload.userProfile.externalUserId,
|
|
208
|
+
icon: profile.payload.userProfile.avatarImageUrl,
|
|
209
|
+
raw: profile
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async get_history() {
|
|
214
|
+
if (!this.access_token) throw new PayPayLoginError("まずはログインしてください");
|
|
215
|
+
|
|
216
|
+
const response = await this.session.get("https://www.paypay.ne.jp/app/v2/bff/getPay2BalanceHistory");
|
|
217
|
+
const history = response.data;
|
|
218
|
+
|
|
219
|
+
if (history.header.resultCode === "S0001") throw new PayPayLoginError(history);
|
|
220
|
+
if (history.header.resultCode !== "S0000") throw new PayPayError(history);
|
|
221
|
+
|
|
222
|
+
return history;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async link_check(url) {
|
|
226
|
+
if (url.includes("https://")) {
|
|
227
|
+
url = url.replace("https://pay.paypay.ne.jp/", "");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const params = { "verificationCode": url };
|
|
231
|
+
const response = await this.session.get("https://www.paypay.ne.jp/app/v2/p2p-api/getP2PLinkInfo", { params });
|
|
232
|
+
const link_info = response.data;
|
|
233
|
+
|
|
234
|
+
if (link_info.header.resultCode !== "S0000") {
|
|
235
|
+
throw new PayPayError(link_info);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
sender_name: link_info.payload.sender.displayName,
|
|
240
|
+
sender_external_id: link_info.payload.sender.externalId,
|
|
241
|
+
sender_icon: link_info.payload.sender.photoUrl,
|
|
242
|
+
order_id: link_info.payload.pendingP2PInfo.orderId,
|
|
243
|
+
chat_room_id: link_info.payload.message.chatRoomId,
|
|
244
|
+
amount: link_info.payload.pendingP2PInfo.amount,
|
|
245
|
+
status: link_info.payload.message.data.status,
|
|
246
|
+
money_light: link_info.payload.message.data.subWalletSplit.senderPrepaidAmount,
|
|
247
|
+
money: link_info.payload.message.data.subWalletSplit.senderEmoneyAmount,
|
|
248
|
+
has_password: link_info.payload.pendingP2PInfo.isSetPasscode,
|
|
249
|
+
raw: link_info
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async link_receive(url, password = null, link_info = null) {
|
|
254
|
+
if (!this.access_token) throw new PayPayLoginError("まずはログインしてください");
|
|
255
|
+
|
|
256
|
+
if (url.includes("https://")) {
|
|
257
|
+
url = url.replace("https://pay.paypay.ne.jp/", "");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!link_info) {
|
|
261
|
+
const params = { "verificationCode": url };
|
|
262
|
+
const response = await this.session.get("https://www.paypay.ne.jp/app/v2/p2p-api/getP2PLinkInfo", { params });
|
|
263
|
+
link_info = response.data;
|
|
264
|
+
if (link_info.header.resultCode !== "S0000") throw new PayPayError(link_info);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (link_info.payload.orderStatus !== "PENDING") {
|
|
268
|
+
throw new PayPayError("すでに 受け取り / 辞退 / キャンセル されているリンクです");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (link_info.payload.pendingP2PInfo.isSetPasscode && password === null) {
|
|
272
|
+
throw new PayPayError("このリンクにはパスワードが設定されています");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const payload = {
|
|
276
|
+
"verificationCode": url,
|
|
277
|
+
"client_uuid": this.client_uuid,
|
|
278
|
+
"requestAt": getJstTime(),
|
|
279
|
+
"requestId": link_info.payload.message.data.requestId,
|
|
280
|
+
"orderId": link_info.payload.message.data.orderId,
|
|
281
|
+
"senderMessageId": link_info.payload.message.messageId,
|
|
282
|
+
"senderChannelUrl": link_info.payload.message.chatRoomId,
|
|
283
|
+
"iosMinimumVersion": "3.45.0",
|
|
284
|
+
"androidMinimumVersion": "3.45.0"
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
if (password) {
|
|
288
|
+
payload["passcode"] = password;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const response = await this.session.post("https://www.paypay.ne.jp/app/v2/p2p-api/acceptP2PSendMoneyLink", payload);
|
|
292
|
+
const receive = response.data;
|
|
293
|
+
|
|
294
|
+
if (receive.header.resultCode === "S0001") throw new PayPayLoginError(receive);
|
|
295
|
+
if (receive.header.resultCode !== "S0000") throw new PayPayError(receive);
|
|
296
|
+
|
|
297
|
+
return receive;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async link_reject(url, link_info = null) {
|
|
301
|
+
if (!this.access_token) throw new PayPayLoginError("まずはログインしてください");
|
|
302
|
+
|
|
303
|
+
if (!link_info) {
|
|
304
|
+
const params = { "verificationCode": url };
|
|
305
|
+
const response = await this.session.get("https://www.paypay.ne.jp/app/v2/p2p-api/getP2PLinkInfo", { params });
|
|
306
|
+
link_info = response.data;
|
|
307
|
+
if (link_info.header.resultCode !== "S0000") throw new PayPayError(link_info);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (link_info.payload.orderStatus !== "PENDING") {
|
|
311
|
+
throw new PayPayError("すでに 受け取り / 辞退 / キャンセル されているリンクです");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const payload = {
|
|
315
|
+
"requestAt": getJstTime(),
|
|
316
|
+
"orderId": link_info.payload.pendingP2PInfo.orderId,
|
|
317
|
+
"verificationCode": url,
|
|
318
|
+
"requestId": uuidv4().toUpperCase(),
|
|
319
|
+
"senderMessageId": link_info.payload.message.messageId,
|
|
320
|
+
"senderChannelUrl": link_info.payload.message.chatRoomId,
|
|
321
|
+
"iosMinimumVersion": "3.45.0",
|
|
322
|
+
"androidMinimumVersion": "3.45.0",
|
|
323
|
+
"client_uuid": this.client_uuid
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const response = await this.session.post("https://www.paypay.ne.jp/app/v2/p2p-api/rejectP2PSendMoneyLink", payload);
|
|
327
|
+
const reject = response.data;
|
|
328
|
+
|
|
329
|
+
if (reject.header.resultCode === "S0001") throw new PayPayLoginError(reject);
|
|
330
|
+
if (reject.header.resultCode !== "S0000") throw new PayPayError(reject);
|
|
331
|
+
|
|
332
|
+
return reject;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async create_p2pcode() {
|
|
336
|
+
if (!this.access_token) throw new PayPayLoginError("まずはログインしてください");
|
|
337
|
+
|
|
338
|
+
const response = await this.session.post("https://www.paypay.ne.jp/app/v1/p2p-api/createP2PCode");
|
|
339
|
+
const create_p2pcode = response.data;
|
|
340
|
+
|
|
341
|
+
if (create_p2pcode.header.resultCode === "S0001") throw new PayPayLoginError(create_p2pcode);
|
|
342
|
+
if (create_p2pcode.header.resultCode !== "S0000") throw new PayPayError(create_p2pcode);
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
p2pcode: create_p2pcode.payload.p2pCode,
|
|
346
|
+
raw: create_p2pcode
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async create_paymentcode(method = "WALLET", method_id = "106177237") {
|
|
351
|
+
if (!this.access_token) throw new PayPayLoginError("まずはログインしてください");
|
|
352
|
+
|
|
353
|
+
const payload = {
|
|
354
|
+
"paymentMethodType": method,
|
|
355
|
+
"paymentMethodId": method_id,
|
|
356
|
+
"paymentCodeSessionId": uuidv4(),
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const response = await this.session.post("https://www.paypay.ne.jp/app/v2/bff/createPaymentOneTimeCodeForHome", payload);
|
|
360
|
+
const paymentcode = response.data;
|
|
361
|
+
|
|
362
|
+
if (paymentcode.header.resultCode === "S0001") throw new PayPayLoginError(paymentcode);
|
|
363
|
+
if (paymentcode.header.resultCode !== "S0000") throw new PayPayError(paymentcode);
|
|
364
|
+
|
|
365
|
+
return paymentcode;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async create_link(amount, password = null) {
|
|
369
|
+
throw new PayPayError("404 Not Found");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async send_money(amount, external_id) {
|
|
373
|
+
throw new PayPayError("404 Not Found");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 他のファイルで const PayPay = require('./paypay'); できるようにエクスポート
|
|
378
|
+
module.exports = PayPay;
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 使い方(Example)
|
|
382
|
+
|
|
383
|
+
別のファイル(例: `main.js`)から呼び出す場合の例です。
|
|
384
|
+
Pythonと異なり、初期化後に `authenticate()` を呼ぶ点に注意してください。
|
|
385
|
+
|
|
386
|
+
```javascript
|
|
387
|
+
const PayPay = require('./paypay'); // 上記ファイルをpaypay.jsとして保存した場合
|
|
388
|
+
|
|
389
|
+
(async () => {
|
|
390
|
+
try {
|
|
391
|
+
// インスタンス作成
|
|
392
|
+
const client = new PayPay({
|
|
393
|
+
phone: "09012345678",
|
|
394
|
+
password: "your_password"
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// ログイン処理 (OTPが必要な場合はここでOTP_REQUIREDが返る想定)
|
|
398
|
+
// すでにアクセストークンを持っている場合は authenticate() をスキップ可能
|
|
399
|
+
const authResult = await client.authenticate();
|
|
400
|
+
|
|
401
|
+
if (authResult && authResult.status === "OTP_REQUIRED") {
|
|
402
|
+
// ここでSMS等で届いたOTPを入力させる処理が必要
|
|
403
|
+
const otp = "1234"; // 例
|
|
404
|
+
await client.login(otp);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 残高確認
|
|
408
|
+
const balance = await client.get_balance();
|
|
409
|
+
console.log("残高:", balance.money_light);
|
|
410
|
+
|
|
411
|
+
// リンクチェック
|
|
412
|
+
const linkInfo = await client.link_check("https://pay.paypay.ne.jp/xxxxxxxxx");
|
|
413
|
+
console.log("リンク情報:", linkInfo);
|
|
414
|
+
|
|
415
|
+
} catch (e) {
|
|
416
|
+
console.error("エラー発生:", e);
|
|
417
|
+
}
|
|
418
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ppaynode",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "module.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"paypay",
|
|
10
|
+
"wrapper"
|
|
11
|
+
],
|
|
12
|
+
"author": "nw-sht",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"description": "paypaythonをforkして作られたものです、要するにpaypay unoffical api wrapperです。axios, uuidに依存して作られています!",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"axios": "^1.13.5",
|
|
17
|
+
"uuid": "^13.0.0"
|
|
18
|
+
}
|
|
19
|
+
}
|