PayPaython-mobile 2.3__py3-none-any.whl

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.
@@ -0,0 +1,3 @@
1
+ from .main import PayPay,PayPayLoginError,PayPayError,PayPayNetWorkError
2
+ __version__ = '2.3'
3
+ __url__ = 'https://github.com/taka-4602/PayPaython-mobile'
@@ -0,0 +1,1086 @@
1
+ import requests
2
+ from uuid import uuid4
3
+ import pkce
4
+ import random
5
+ from typing import NamedTuple
6
+
7
+ def generate_sentry():
8
+ trace_id = uuid4().hex
9
+ span_id = uuid4().hex[16:]
10
+
11
+ class SENTRY_TRACE_SPAN(NamedTuple):
12
+ trace_id:str
13
+ span_id:str
14
+ sentry_trace:str
15
+ sentry_trace_0:str
16
+ sentry_trace_1:str
17
+
18
+ return SENTRY_TRACE_SPAN(trace_id, span_id, f"{trace_id}-{span_id}", f"{trace_id}-{span_id}-0", f"{trace_id}-{span_id}-1")
19
+
20
+ def generate_vector(r1, r2, r3, precision=8):
21
+ v1 = f"{random.uniform(*r1):.{precision}f}"
22
+ v2 = f"{random.uniform(*r2):.{precision}f}"
23
+ v3 = f"{random.uniform(*r3):.{precision}f}"
24
+ return f"{v1}_{v2}_{v3}"
25
+
26
+ def generate_device_state():
27
+ device_orientation = generate_vector(
28
+ (2.2, 2.6),
29
+ (-0.2, -0.05),
30
+ (-0.05, 0.1)
31
+ )
32
+ device_orientation_2 = generate_vector(
33
+ (2.0, 2.6),
34
+ (-0.2, -0.05),
35
+ (-0.05, 0.2)
36
+ )
37
+ device_rotation = generate_vector(
38
+ (-0.8, -0.6),
39
+ (0.65, 0.8),
40
+ (-0.12, -0.04)
41
+ )
42
+ device_rotation_2 = generate_vector(
43
+ (-0.85, -0.4),
44
+ (0.53, 0.9),
45
+ (-0.15, -0.03)
46
+ )
47
+ device_acceleration = generate_vector(
48
+ (-0.35, 0.0),
49
+ (-0.01, 0.3),
50
+ (-0.1, 0.1)
51
+ )
52
+ device_acceleration_2 = generate_vector(
53
+ (0.01, 0.04),
54
+ (-0.04, 0.09),
55
+ (-0.03, 0.1)
56
+ )
57
+ class DeviceHeaders(NamedTuple):
58
+ device_orientation: str
59
+ device_orientation_2: str
60
+ device_rotation: str
61
+ device_rotation_2: str
62
+ device_acceleration: str
63
+ device_acceleration_2: str
64
+
65
+ return DeviceHeaders(
66
+ device_orientation,
67
+ device_orientation_2,
68
+ device_rotation,
69
+ device_rotation_2,
70
+ device_acceleration,
71
+ device_acceleration_2
72
+ )
73
+
74
+ def update_header_device_state(headers:dict):
75
+ device_state = generate_device_state()
76
+ headers["Device-Orientation"] = device_state.device_orientation
77
+ headers["Device-Orientation-2"] = device_state.device_orientation_2
78
+ headers["Device-Rotation"] = device_state.device_rotation
79
+ headers["Device-Rotation-2"] = device_state.device_rotation_2
80
+ headers["Device-Acceleration"] = device_state.device_acceleration
81
+ headers["Device-Acceleration-2"] = device_state.device_acceleration_2
82
+
83
+ return headers
84
+
85
+ sentry_public_key="e5f3c063d55d3058bc5bfb0f311152e4"
86
+ def update_header_baggage(header:dict,public_key:str,sample_rate:str=None,sampled:bool=None,transaction:str=None,sentry_trace_style:int=None):
87
+ baggage = "sentey-environment=Production," + f"sentry-public_key={public_key},sentry-release=consumer-android%404.78.1%2B47801"
88
+ if sample_rate:
89
+ baggage = baggage + f",sentry-sample_rate={sample_rate}"
90
+
91
+ if sampled!=None:
92
+ if sampled:
93
+ baggage = baggage + ",sentry-sampled=true"
94
+ else:
95
+ baggage = baggage + ",sentry-sampled=false"
96
+
97
+ sentry_ids = generate_sentry()
98
+ baggage = baggage + f",sentry-trace_id={sentry_ids.trace_id}"
99
+
100
+ if transaction:
101
+ baggage = baggage + f",sentry-transaction={transaction}"
102
+
103
+ if sentry_trace_style == 0:
104
+ header["sentry-trace"] = sentry_ids.sentry_trace_0
105
+ elif sentry_trace_style == 1:
106
+ header["sentry-trace"] = sentry_ids.sentry_trace_1
107
+ else:
108
+ header["sentry-trace"] = sentry_ids.sentry_trace
109
+
110
+ header["baggage"] = baggage
111
+ return header
112
+
113
+ class PayPayError(Exception):
114
+ pass
115
+ class PayPayLoginError(Exception):
116
+ pass
117
+ class PayPayNetWorkError(Exception):
118
+ pass
119
+ class PayPay():
120
+ def __init__(self,phone:str=None,password:str=None,device_uuid:str=None,client_uuid:str=str(uuid4()),access_token:str=None,proxy=None):
121
+
122
+ if phone and "-" in phone:
123
+ phone=phone.replace("-","")
124
+
125
+ self.session=requests.Session()
126
+
127
+ if device_uuid:
128
+ self.device_uuid=device_uuid
129
+ else:
130
+ self.device_uuid=str(uuid4())
131
+
132
+ self.client_uuid=client_uuid
133
+
134
+ if isinstance(proxy, str):
135
+ if not "http" in proxy:
136
+ proxy = "http://" + proxy
137
+ self.proxy={"https":proxy,"http":proxy}
138
+
139
+ elif isinstance(proxy, dict):
140
+ self.proxy=proxy
141
+
142
+ else:
143
+ self.proxy=proxy
144
+
145
+ self.params={
146
+ "payPayLang":"ja"
147
+ }
148
+ #try:
149
+ # iosstore=self.session.get("https://apps.apple.com/jp/app/paypay-%E3%83%9A%E3%82%A4%E3%83%9A%E3%82%A4/id1435783608",proxies=self.proxy)
150
+ #except Exception as e:
151
+ # raise NetWorkError(e)
152
+
153
+ self.version="5.11.1" #BeautifulSoup(iosstore.text,"html.parser").find(class_="l-column small-6 medium-12 whats-new__latest__version").text.split()[1]
154
+ device_state = generate_device_state()
155
+ self.headers = {
156
+ "Accept": "*/*",
157
+ "Accept-Charset": "UTF-8",
158
+ "Accept-Encoding": "gzip",
159
+ "Client-Mode": "NORMAL",
160
+ "Client-OS-Release-Version": "10",
161
+ "Client-OS-Type": "ANDROID",
162
+ "Client-OS-Version": "29.0.0",
163
+ "Client-Type": "PAYPAYAPP",
164
+ "Client-UUID": self.client_uuid,
165
+ "Client-Version": self.version,
166
+ "Connection": "Keep-Alive",
167
+ "Content-Type": "application/x-www-form-urlencoded",
168
+ "Device-Acceleration": device_state.device_acceleration,
169
+ "Device-Acceleration-2": device_state.device_acceleration_2,
170
+ "Device-Brand-Name": "KDDI",
171
+ "Device-Hardware-Name": "qcom",
172
+ "Device-In-Call": "false",
173
+ "Device-Lock-App-Setting": "false",
174
+ "Device-Lock-Type": "NONE",
175
+ "Device-Manufacturer-Name": "samsung",
176
+ "Device-Name": "SCV38",
177
+ "Device-Orientation": device_state.device_orientation,
178
+ "Device-Orientation-2": device_state.device_orientation_2,
179
+ "Device-Rotation": device_state.device_rotation,
180
+ "Device-Rotation-2": device_state.device_rotation_2,
181
+ "Device-UUID": self.device_uuid,
182
+ "Host": "app4.paypay.ne.jp",
183
+ "Is-Emulator": "false",
184
+ "Network-Status": "WIFI",
185
+ "System-Locale": "ja",
186
+ "Timezone": "Asia/Tokyo",
187
+ "User-Agent": f"PaypayApp/{self.version} Android10"
188
+ }
189
+ if access_token:
190
+ self.access_token=access_token
191
+ self.headers["Authorization"]=f"Bearer {self.access_token}"
192
+ self.headers["content-type"]="application/json"
193
+
194
+ elif phone:
195
+ self.access_token=None
196
+ self.refresh_token=None
197
+ self.code_verifier, self.code_challenge = pkce.generate_pkce_pair(43)
198
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0",False,"OAuth2Fragment",0)
199
+
200
+ payload = {
201
+ "clientId": "pay2-mobile-app-client",
202
+ "clientAppVersion": self.version,
203
+ "clientOsVersion": "29.0.0",
204
+ "clientOsType": "ANDROID",
205
+ "redirectUri": "paypay://oauth2/callback",
206
+ "responseType": "code",
207
+ "state": pkce.generate_code_verifier(43),
208
+ "codeChallenge": self.code_challenge,
209
+ "codeChallengeMethod": "S256",
210
+ "scope": "REGULAR",
211
+ "tokenVersion": "v2",
212
+ "prompt": "",
213
+ "uiLocales": "ja"
214
+ }
215
+ par=self.session.post("https://app4.paypay.ne.jp/bff/v2/oauth2/par?payPayLang=ja",headers=self.headers,data=payload,proxies=self.proxy)
216
+ try:
217
+ par=par.json()
218
+ except:
219
+ raise PayPayNetWorkError("日本以外からは接続できません")
220
+
221
+ if par["header"]["resultCode"] != "S0000":
222
+ raise PayPayLoginError(par)
223
+
224
+ headers = {
225
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
226
+ "Accept-Encoding": "gzip, deflate, br, zstd",
227
+ "Accept-Language": "ja-JP,ja;q=0.9",
228
+ "Cache-Control": "no-cache",
229
+ "Connection": "keep-alive",
230
+ "Host": "www.paypay.ne.jp",
231
+ "is-emulator": "false",
232
+ "Pragma": "no-cache",
233
+ "sec-ch-ua": '"Not A(Brand";v="8", "Chromium";v="132", "Android WebView";v="132"',
234
+ "sec-ch-ua-mobile": "?1",
235
+ "sec-ch-ua-platform": '"Android"',
236
+ "Sec-Fetch-Dest": "document",
237
+ "Sec-Fetch-Mode": "navigate",
238
+ "Sec-Fetch-Site": "none",
239
+ "Sec-Fetch-User": "?1",
240
+ "Upgrade-Insecure-Requests": "1",
241
+ "User-Agent": f"Mozilla/5.0 (Linux; Android 10; SCV38 Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.163 Mobile Safari/537.36 jp.pay2.app.android/{self.version}",
242
+ "X-Requested-With": "jp.ne.paypay.android.app"
243
+ }
244
+ params = {
245
+ "client_id": "pay2-mobile-app-client",
246
+ "request_uri": par["payload"]["requestUri"]
247
+ }
248
+ self.session.get(f"https://www.paypay.ne.jp/portal/api/v2/oauth2/authorize",headers=headers,params=params,proxies=self.proxy)
249
+ params = {
250
+ "client_id": "pay2-mobile-app-client",
251
+ "mode": "landing"
252
+ }
253
+ self.session.get("https://www.paypay.ne.jp/portal/oauth2/sign-in",headers=headers,params=params,proxies=self.proxy)
254
+
255
+ sentry_ids = generate_sentry()
256
+ headers = {
257
+ "Accept": "application/json, text/plain, */*",
258
+ "Accept-Encoding": "gzip, deflate, br, zstd",
259
+ "Accept-Language": "ja-JP,ja;q=0.9",
260
+ "baggage": f"sentry-environment=Production,sentry-release=4.75.0,sentry-public_key=a5e3ae80a20e15b8de50274dd231ab83,sentry-trace_id={sentry_ids.trace_id},sentry-sample_rate=0.0005,sentry-transaction=SignIn,sentry-sampled=false",
261
+ "Cache-Control": "no-cache",
262
+ "Client-Id": "pay2-mobile-app-client",
263
+ "Client-Type": "PAYPAYAPP",
264
+ "Connection": "keep-alive",
265
+ "Host": "www.paypay.ne.jp",
266
+ "Pragma": "no-cache",
267
+ "Referer": "https://www.paypay.ne.jp/portal/oauth2/sign-in?client_id=pay2-mobile-app-client&mode=landing",
268
+ "sec-ch-ua": '"Not A(Brand";v="8", "Chromium";v="132", "Android WebView";v="132")',
269
+ "sec-ch-ua-mobile": "?1",
270
+ "sec-ch-ua-platform": '"Android"',
271
+ "Sec-Fetch-Dest": "empty",
272
+ "Sec-Fetch-Mode": "cors",
273
+ "Sec-Fetch-Site": "same-origin",
274
+ "sentry-trace": sentry_ids.sentry_trace_0,
275
+ "User-Agent": f"Mozilla/5.0 (Linux; Android 10; SCV38 Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.163 Mobile Safari/537.36 jp.pay2.app.android/{self.version}",
276
+ "X-Requested-With": "jp.ne.paypay.android.app"
277
+ }
278
+ par_check=self.session.get("https://www.paypay.ne.jp/portal/api/v2/oauth2/par/check",headers=headers,proxies=self.proxy).json()
279
+ if par_check["header"]["resultCode"] != "S0000":
280
+ raise PayPayLoginError(par_check)
281
+
282
+ sentry_ids = generate_sentry()
283
+ headers = {
284
+ "Accept": "application/json, text/plain, */*",
285
+ "Accept-Encoding": "gzip, deflate, br, zstd",
286
+ "Accept-Language": "ja-JP,ja;q=0.9",
287
+ "baggage": f"sentry-environment=Production,sentry-release=4.75.0,sentry-public_key=a5e3ae80a20e15b8de50274dd231ab83,sentry-trace_id={sentry_ids.trace_id}",
288
+ "Cache-Control": "no-cache",
289
+ "Client-Id": "pay2-mobile-app-client",
290
+ "Client-OS-Type": "ANDROID",
291
+ "Client-OS-Version": "29.0.0",
292
+ "Client-Type": "PAYPAYAPP",
293
+ "Client-Version": self.version,
294
+ "Connection": "keep-alive",
295
+ "Content-Type": "application/json",
296
+ "Host": "www.paypay.ne.jp",
297
+ "Origin": "https://www.paypay.ne.jp",
298
+ "Pragma": "no-cache",
299
+ "Referer": "https://www.paypay.ne.jp/portal/oauth2/sign-in?client_id=pay2-mobile-app-client&mode=landing",
300
+ "sec-ch-ua": '"Not A(Brand";v="8", "Chromium";v="132", "Android WebView";v="132")',
301
+ "sec-ch-ua-mobile": "?1",
302
+ "sec-ch-ua-platform": '"Android"',
303
+ "Sec-Fetch-Dest": "empty",
304
+ "Sec-Fetch-Mode": "cors",
305
+ "Sec-Fetch-Site": "same-origin",
306
+ "sentry-trace": sentry_ids.sentry_trace,
307
+ "User-Agent": f"Mozilla/5.0 (Linux; Android 10; SCV38 Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.163 Mobile Safari/537.36 jp.pay2.app.android/{self.version}",
308
+ "X-Requested-With": "jp.ne.paypay.android.app"
309
+ }
310
+ payload={
311
+ "username":phone,
312
+ "password":password,
313
+ "signInAttemptCount":1
314
+ }
315
+ signin=self.session.post("https://www.paypay.ne.jp/portal/api/v2/oauth2/sign-in/password",headers=headers,json=payload,proxies=self.proxy).json()
316
+ if signin["header"]["resultCode"] != "S0000":
317
+ raise PayPayLoginError(signin)
318
+
319
+ if device_uuid:
320
+ try:
321
+ uri=signin["payload"]["redirectUrl"].replace("paypay://oauth2/callback?","").split("&")
322
+ except:
323
+ raise PayPayLoginError("登録されていないDevice-UUID")
324
+
325
+ headers = self.headers
326
+ del headers["Device-Lock-Type"]
327
+ del headers["Device-Lock-App-Setting"]
328
+ del headers["baggage"]
329
+ del headers["sentry-trace"]
330
+
331
+
332
+
333
+ confirm_data={
334
+ "clientId":"pay2-mobile-app-client",
335
+ "redirectUri":"paypay://oauth2/callback",
336
+ "code":uri[0].replace("code=",""),
337
+ "codeVerifier":self.code_verifier
338
+ }
339
+ get_token=self.session.post("https://app4.paypay.ne.jp/bff/v2/oauth2/token",headers=headers,data=confirm_data,params=self.params,proxies=self.proxy).json()
340
+ if get_token["header"]["resultCode"] != "S0000":
341
+ raise PayPayLoginError(get_token)
342
+
343
+ self.access_token=get_token["payload"]["accessToken"]
344
+ self.refresh_token=get_token["payload"]["refreshToken"]
345
+ self.headers["Authorization"]=f"Bearer {self.access_token}"
346
+ self.headers["content-type"]="application/json"
347
+ self.headers=update_header_device_state(self.headers)
348
+
349
+ else:
350
+ code_update=self.session.post("https://www.paypay.ne.jp/portal/api/v2/oauth2/extension/code-grant/update",headers=headers,json={},proxies=self.proxy).json()
351
+ if code_update["header"]["resultCode"] != "S0000":
352
+ raise PayPayLoginError(code_update)
353
+
354
+ headers["Referer"]="https://www.paypay.ne.jp/portal/oauth2/verification-method?client_id=pay2-mobile-app-client&mode=navigation-2fa"
355
+ payload={
356
+ "params":{
357
+ "extension_id":"user-main-2fa-v1",
358
+ "data":{
359
+ "type":"SELECT_FLOW",
360
+ "payload":{
361
+ "flow":"OTL",
362
+ "sign_in_method":"MOBILE",
363
+ "base_url":"https://www.paypay.ne.jp/portal/oauth2/l"
364
+ }
365
+ }
366
+ }
367
+ }
368
+
369
+ nav_2fa=self.session.post("https://www.paypay.ne.jp/portal/api/v2/oauth2/extension/code-grant/update",headers=headers,json=payload,proxies=self.proxy).json()
370
+ if nav_2fa["header"]["resultCode"] != "S0000":
371
+ raise PayPayLoginError(nav_2fa)
372
+
373
+ headers["Referer"]="https://www.paypay.ne.jp/portal/oauth2/otl-request?client_id=pay2-mobile-app-client&mode=navigation-2fa"
374
+ otl_request=self.session.post("https://www.paypay.ne.jp/portal/api/v2/oauth2/extension/code-grant/side-channel/next-action-polling",headers=headers,json={"waitUntil": "PT5S"},proxies=self.proxy).json()
375
+ if otl_request["header"]["resultCode"] != "S0000":
376
+ raise PayPayLoginError(otl_request)
377
+
378
+ def login(self,url:str):
379
+ if "https://" in url:
380
+ url=url.replace("https://www.paypay.ne.jp/portal/oauth2/l?id=","")
381
+
382
+ sentry_ids = generate_sentry()
383
+ headers = {
384
+ "Accept": "application/json, text/plain, */*",
385
+ "Accept-Encoding": "gzip, deflate, br, zstd",
386
+ "Accept-Language": "ja-JP,ja;q=0.9",
387
+ "baggage": f"sentry-environment=Production,sentry-release=4.75.0,sentry-public_key=a5e3ae80a20e15b8de50274dd231ab83,sentry-trace_id={sentry_ids.trace_id},sentry-sample_rate=0.0005,sentry-transaction=OTL,sentry-sampled=false",
388
+ "Cache-Control": "no-cache",
389
+ "Client-Id": "pay2-mobile-app-client",
390
+ "Client-OS-Type": "ANDROID",
391
+ "Client-OS-Version": "29.0.0",
392
+ "Client-Type": "PAYPAYAPP",
393
+ "Client-Version": self.version,
394
+ "Connection": "keep-alive",
395
+ "Content-Type": "application/json",
396
+ "Host": "www.paypay.ne.jp",
397
+ "Origin": "https://www.paypay.ne.jp",
398
+ "Pragma": "no-cache",
399
+ "Referer": f"https://www.paypay.ne.jp/portal/oauth2/l?id={url}&client_id=pay2-mobile-app-client",
400
+ "sec-ch-ua": '"Not A(Brand";v="8", "Chromium";v="132", "Android WebView";v="132")',
401
+ "sec-ch-ua-mobile": "?1",
402
+ "sec-ch-ua-platform": '"Android"',
403
+ "Sec-Fetch-Dest": "empty",
404
+ "Sec-Fetch-Mode": "cors",
405
+ "Sec-Fetch-Site": "same-origin",
406
+ "sentry-trace": sentry_ids.sentry_trace_0,
407
+ "User-Agent": f"Mozilla/5.0 (Linux; Android 10; SCV38 Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.163 Mobile Safari/537.36 jp.pay2.app.android/{self.version}",
408
+ "X-Requested-With": "jp.ne.paypay.android.app"
409
+ }
410
+ confirm_url=self.session.post("https://www.paypay.ne.jp/portal/api/v2/oauth2/extension/sign-in/2fa/otl/verify",headers=headers,json={"code":url},proxies=self.proxy).json()
411
+ if confirm_url["header"]["resultCode"] != "S0000":
412
+ raise PayPayLoginError(confirm_url)
413
+
414
+ payload={
415
+ "params":{
416
+ "extension_id":"user-main-2fa-v1",
417
+ "data":{
418
+ "type":"COMPLETE_OTL",
419
+ "payload":None
420
+ }
421
+ }
422
+ }
423
+ get_uri=self.session.post("https://www.paypay.ne.jp/portal/api/v2/oauth2/extension/code-grant/update",headers=headers,json=payload,proxies=self.proxy).json()
424
+ if get_uri["header"]["resultCode"] != "S0000":
425
+ raise PayPayLoginError(get_uri)
426
+
427
+ try:
428
+ uri=get_uri["payload"]["redirect_uri"].replace("paypay://oauth2/callback?","").split("&")
429
+ except:
430
+ raise PayPayLoginError('redirect_uriが見つかりませんでした\n'+str(get_uri))
431
+
432
+ headers = self.headers
433
+ del headers["Device-Lock-Type"]
434
+ del headers["Device-Lock-App-Setting"]
435
+ del headers["baggage"]
436
+ del headers["sentry-trace"]
437
+
438
+ confirm_data={
439
+ "clientId":"pay2-mobile-app-client",
440
+ "redirectUri":"paypay://oauth2/callback",
441
+ "code":uri[0].replace("code=",""),
442
+ "codeVerifier":self.code_verifier
443
+ }
444
+ get_token=self.session.post("https://app4.paypay.ne.jp/bff/v2/oauth2/token",headers=headers,data=confirm_data,params=self.params,proxies=self.proxy).json()
445
+ if get_token["header"]["resultCode"] != "S0000":
446
+ raise PayPayLoginError(get_token)
447
+
448
+ self.access_token=get_token["payload"]["accessToken"] #90日もつよ
449
+ self.refresh_token=get_token["payload"]["refreshToken"]
450
+ self.headers["Authorization"]=f"Bearer {self.access_token}"
451
+ self.headers["content-type"]="application/json"
452
+ self.headers=update_header_device_state(self.headers)
453
+
454
+ return get_token
455
+
456
+ def token_refresh(self,refresh_token:str) -> dict:
457
+ if not self.access_token:
458
+ raise PayPayLoginError("まずはログインしてください")
459
+
460
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0",False,"OAuth2Fragment",0)
461
+ refdata={
462
+ "clientId": "pay2-mobile-app-client",
463
+ "refreshToken": refresh_token,
464
+ "tokenVersion": "v2"
465
+ }
466
+ refresh=self.session.post("https://app4.paypay.ne.jp/bff/v2/oauth2/refresh",headers=self.headers,data=refdata,proxies=self.proxy).json()
467
+
468
+ if refresh["header"]["resultCode"] == "S0001" or refresh["header"]["resultCode"] == "S1003":
469
+ raise PayPayLoginError(refresh)
470
+
471
+ if refresh["header"]["resultCode"] == "S0003":
472
+ raise PayPayLoginError(refresh)
473
+
474
+ if refresh["header"]["resultCode"] != "S0000":
475
+ raise PayPayError(refresh)
476
+
477
+ self.access_token=refresh["payload"]["accessToken"]
478
+ self.refresh_token=refresh["payload"]["refreshToken"]
479
+ self.headers["Authorization"]=f"Bearer {refresh['payload']['accessToken']}"
480
+
481
+ return refresh
482
+
483
+ def get_history(self,size:int=20,cashback:bool=False) -> dict:
484
+ if not self.access_token:
485
+ raise PayPayLoginError("まずはログインしてください")
486
+
487
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0.0099999997764826",False,"TransactionHistoryV2Fragment",0)
488
+
489
+ params = {
490
+ "pageSize": str(size),
491
+ "orderTypes": "",
492
+ "paymentMethodTypes": "",
493
+ "signUpCompletedAt": "2021-01-02T10:16:24Z",
494
+ "isOverdraftOnly": "false",
495
+ "payPayLang": "ja"
496
+ }
497
+ if cashback:
498
+ params["orderTypes"] = "CASHBACK"
499
+
500
+ history = self.session.get(f"https://app4.paypay.ne.jp/bff/v3/getPaymentHistory",params=params,headers=self.headers,proxies=self.proxy).json()
501
+
502
+ if history["header"]["resultCode"] == "S0001":
503
+ raise PayPayLoginError(history)
504
+
505
+ if history["header"]["resultCode"] != "S0000":
506
+ raise PayPayError(history)
507
+
508
+ return history
509
+
510
+ def get_balance(self):
511
+ if not self.access_token:
512
+ raise PayPayLoginError("まずはログインしてください")
513
+
514
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0",False,"WalletAssetDetailsFragment")
515
+
516
+ params = {
517
+ "includePendingBonusLite": "false",
518
+ "includePending": "true",
519
+ "noCache": "true",
520
+ "includeKycInfo": "true",
521
+ "includePayPaySecuritiesInfo": "true",
522
+ "includePointInvestmentInfo": "true",
523
+ "includePayPayBankInfo": "true",
524
+ "includeGiftVoucherInfo": "true",
525
+ "payPayLang": "ja"
526
+ }
527
+ balance=self.session.get("https://app4.paypay.ne.jp/bff/v1/getBalanceInfo",headers=self.headers,params=params,proxies=self.proxy).json()
528
+
529
+ if balance["header"]["resultCode"] == "S0001":
530
+ raise PayPayLoginError(balance)
531
+
532
+ if balance["header"]["resultCode"] != "S0000":
533
+ raise PayPayError(balance)
534
+
535
+ try:
536
+ money=balance["payload"]["walletDetail"]["emoneyBalanceInfo"]["balance"]
537
+ except:
538
+ money=None
539
+
540
+ class GetBalance(NamedTuple):
541
+ money: int
542
+ money_light: int
543
+ all_balance: int
544
+ useable_balance: int
545
+ points: int
546
+ raw: dict
547
+
548
+ money_light=balance["payload"]["walletDetail"]["prepaidBalanceInfo"]["balance"]
549
+ all_balance=balance["payload"]["walletSummary"]["allTotalBalanceInfo"]["balance"]
550
+ useable_balance=balance["payload"]["walletSummary"]["usableBalanceInfoWithoutCashback"]["balance"]
551
+ points=balance["payload"]["walletDetail"]["cashBackBalanceInfo"]["balance"]
552
+
553
+ return GetBalance(money,money_light,all_balance,useable_balance,points,balance)
554
+
555
+ def link_check(self,url:str,web_api:bool=False):
556
+ if "https://" in url:
557
+ url=url.replace("https://pay.paypay.ne.jp/","")
558
+
559
+ if web_api:
560
+ headers={
561
+ "Accept":"application/json, text/plain, */*",
562
+ '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',
563
+ "Content-Type":"application/json"
564
+ }
565
+ link_info=requests.get(f"https://www.paypay.ne.jp/app/v2/p2p-api/getP2PLinkInfo?verificationCode={url}",headers=headers,proxies=self.proxy).json()
566
+
567
+ else:
568
+ if not self.access_token:
569
+ raise PayPayLoginError("まずはログインしてください")
570
+
571
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0.0099999997764826",False,"P2PMoneyTransferDetailFragment",0)
572
+ params={
573
+ "verificationCode": url,
574
+ "payPayLang": "ja"
575
+ }
576
+ link_info=self.session.get("https://app4.paypay.ne.jp/bff/v2/getP2PLinkInfo",headers=self.headers,params=params,proxies=self.proxy).json()
577
+
578
+ if link_info["header"]["resultCode"] == "S0001":
579
+ raise PayPayLoginError(link_info)
580
+
581
+ if link_info["header"]["resultCode"] != "S0000":
582
+ raise PayPayError(link_info)
583
+
584
+ class LinkInfo(NamedTuple):
585
+ sender_name: str
586
+ sender_external_id: str
587
+ sender_icon: str
588
+ order_id: str
589
+ chat_room_id: str
590
+ amount: int
591
+ status: str
592
+ money_light: int
593
+ money: int
594
+ has_password: bool
595
+ raw: dict
596
+
597
+ sender_name=link_info["payload"]["sender"]["displayName"]
598
+ sender_external_id=link_info["payload"]["sender"]["externalId"]
599
+ sender_icon=link_info["payload"]["sender"]["photoUrl"]
600
+ order_id=link_info["payload"]["pendingP2PInfo"]["orderId"]
601
+ chat_room_id=link_info["payload"]["message"]["chatRoomId"]
602
+ amount=link_info["payload"]["pendingP2PInfo"]["amount"]
603
+ status=link_info["payload"]["message"]["data"]["status"]
604
+ money_light=link_info["payload"]["message"]["data"]["subWalletSplit"]["senderPrepaidAmount"]
605
+ money=link_info["payload"]["message"]["data"]["subWalletSplit"]["senderEmoneyAmount"]
606
+ has_password=link_info["payload"]["pendingP2PInfo"]["isSetPasscode"]
607
+
608
+ return LinkInfo(sender_name,sender_external_id,sender_icon,order_id,chat_room_id,amount,status,money_light,money,has_password,link_info)
609
+
610
+ def link_receive(self,url:str,passcode:str=None,link_info:dict=None) -> dict:
611
+ if not self.access_token:
612
+ raise PayPayLoginError("まずはログインしてください")
613
+
614
+ if "https://" in url:
615
+ url=url.replace("https://pay.paypay.ne.jp/","")
616
+
617
+ if not link_info:
618
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0.0099999997764826",False,"P2PMoneyTransferDetailFragment",0)
619
+ params={
620
+ "verificationCode": url,
621
+ "payPayLang": "ja"
622
+ }
623
+ link_info=self.session.get("https://app4.paypay.ne.jp/bff/v2/getP2PLinkInfo",headers=self.headers,params=params,proxies=self.proxy).json()
624
+
625
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
626
+ payload={
627
+ "requestId":str(uuid4()),
628
+ "orderId":link_info["payload"]["pendingP2PInfo"]["orderId"],
629
+ "verificationCode":url,
630
+ "passcode":None,
631
+ "senderMessageId":link_info["payload"]["message"]["messageId"],
632
+ "senderChannelUrl":link_info["payload"]["message"]["chatRoomId"]
633
+ }
634
+
635
+ if link_info["header"]["resultCode"] == "S0001":
636
+ raise PayPayLoginError(link_info)
637
+
638
+ if link_info["header"]["resultCode"] != "S0000":
639
+ raise PayPayError(link_info)
640
+
641
+ if link_info["payload"]["orderStatus"] != "PENDING":
642
+ raise PayPayError("すでに 受け取り / 辞退 / キャンセル されているリンクです")
643
+
644
+ if link_info["payload"]["pendingP2PInfo"]["isSetPasscode"] and passcode==None:
645
+ raise PayPayError("このリンクにはパスワードが設定されています")
646
+
647
+ if link_info["payload"]["pendingP2PInfo"]["isSetPasscode"]:
648
+ payload["passcode"] = passcode
649
+
650
+ receive = self.session.post("https://app4.paypay.ne.jp/bff/v2/acceptP2PSendMoneyLink",headers=self.headers,json=payload,params=self.params,proxies=self.proxy)
651
+ try:
652
+ receive=receive.json()
653
+ except:
654
+ raise PayPayNetWorkError("日本以外からは接続できません")
655
+
656
+ if receive["header"]["resultCode"] == "S0001":
657
+ raise PayPayLoginError(receive)
658
+
659
+ if receive["header"]["resultCode"] != "S0000":
660
+ raise PayPayError(receive)
661
+
662
+ return receive
663
+
664
+ def link_reject(self,url:str,link_info:dict=None) -> dict:
665
+ if not self.access_token:
666
+ raise PayPayLoginError("まずはログインしてください")
667
+
668
+ if "https://" in url:
669
+ url=url.replace("https://pay.paypay.ne.jp/","")
670
+
671
+ if not link_info:
672
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0.0099999997764826",False,"P2PMoneyTransferDetailFragment",0)
673
+ params={
674
+ "verificationCode": url,
675
+ "payPayLang": "ja"
676
+ }
677
+ link_info=self.session.get("https://app4.paypay.ne.jp/bff/v2/getP2PLinkInfo",headers=self.headers,params=params,proxies=self.proxy).json()
678
+
679
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
680
+ payload={
681
+ "requestId":str(uuid4()),
682
+ "orderId":link_info["payload"]["pendingP2PInfo"]["orderId"],
683
+ "verificationCode":url,
684
+ "senderMessageId":link_info["payload"]["message"]["messageId"],
685
+ "senderChannelUrl":link_info["payload"]["message"]["chatRoomId"]
686
+ }
687
+ if link_info["header"]["resultCode"] == "S0001":
688
+ raise PayPayLoginError(link_info)
689
+
690
+ if link_info["header"]["resultCode"] != "S0000":
691
+ raise PayPayError(link_info)
692
+
693
+ if link_info["payload"]["orderStatus"] != "PENDING":
694
+ raise PayPayError("すでに 受け取り / 辞退 / キャンセル されているリンクです")
695
+
696
+ reject=self.session.post("https://app4.paypay.ne.jp/bff/v2/rejectP2PSendMoneyLink",headers=self.headers,json=payload,params=self.params,proxies=self.proxy).json()
697
+
698
+ if reject["header"]["resultCode"] == "S0001":
699
+ raise PayPayLoginError(reject)
700
+
701
+ if reject["header"]["resultCode"] != "S0000":
702
+ raise PayPayError(reject)
703
+
704
+ return reject
705
+
706
+ def link_cancel(self,url:str,link_info:dict=None) -> dict:
707
+ if not self.access_token:
708
+ raise PayPayLoginError("まずはログインしてください")
709
+
710
+ if "https://" in url:
711
+ url=url.replace("https://pay.paypay.ne.jp/","")
712
+ if not link_info:
713
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0.0099999997764826",False,"P2PMoneyTransferDetailFragment",0)
714
+ params={
715
+ "verificationCode": url,
716
+ "payPayLang": "ja"
717
+ }
718
+ link_info=self.session.get("https://app4.paypay.ne.jp/bff/v2/getP2PLinkInfo",headers=self.headers,params=params,proxies=self.proxy).json()
719
+
720
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
721
+ payload={
722
+ "orderId":link_info["payload"]["pendingP2PInfo"]["orderId"],
723
+ "requestId":str(uuid4()),
724
+ "verificationCode":url,
725
+ }
726
+ if link_info["header"]["resultCode"] == "S0001":
727
+ raise PayPayLoginError(link_info)
728
+
729
+ if link_info["header"]["resultCode"] != "S0000":
730
+ raise PayPayError(link_info)
731
+
732
+ if link_info["payload"]["orderStatus"] != "PENDING":
733
+ raise PayPayError("すでに 受け取り / 辞退 / キャンセル されているリンクです")
734
+
735
+ cancel=self.session.post("https://app4.paypay.ne.jp/p2p/v1/cancelP2PSendMoneyLink",headers=self.headers,json=payload,params=self.params,proxies=self.proxy).json()
736
+
737
+ if cancel["header"]["resultCode"] == "S0001":
738
+ raise PayPayLoginError(cancel)
739
+
740
+ if cancel["header"]["resultCode"] != "S0000":
741
+ raise PayPayError(cancel)
742
+
743
+ return cancel
744
+
745
+ def create_link(self,amount:int,passcode:str=None,pochibukuro:bool=False,theme:str="default-sendmoney"):
746
+ if not self.access_token:
747
+ raise PayPayLoginError("まずはログインしてください")
748
+
749
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
750
+ payload={
751
+ "requestId":str(uuid4()),
752
+ "amount":amount,
753
+ "socketConnection": "P2P",
754
+ "theme":theme,
755
+ "source":"sendmoney_home_sns"
756
+ }
757
+ if passcode:
758
+ payload["passcode"]=passcode
759
+ if pochibukuro:
760
+ payload["theme"]="pochibukuro"
761
+ create=self.session.post("https://app4.paypay.ne.jp/bff/v2/executeP2PSendMoneyLink",headers=self.headers,json=payload,params=self.params,proxies=self.proxy)
762
+ try:
763
+ create=create.json()
764
+ except:
765
+ raise PayPayNetWorkError("日本以外からは接続できません")
766
+
767
+ if create["header"]["resultCode"] == "S0001":
768
+ raise PayPayLoginError(create)
769
+
770
+ if create["header"]["resultCode"] != "S0000":
771
+ raise PayPayError(create)
772
+
773
+ class CreateLink(NamedTuple):
774
+ link: str
775
+ chat_room_id: str
776
+ order_id: str
777
+ raw: dict
778
+
779
+ link=create["payload"]["link"]
780
+ chat_room_id=create["payload"]["chatRoomId"]
781
+ order_id=create["payload"]["orderId"]
782
+
783
+ return CreateLink(link,chat_room_id,order_id,create)
784
+
785
+ def send_money(self,amount:int,receiver_id:str,pochibukuro:bool=False,theme:str="default-sendmoney"):
786
+ if not self.access_token:
787
+ raise PayPayLoginError("まずはログインしてください")
788
+
789
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
790
+ payload = {
791
+ "amount":amount,
792
+ "theme":theme,
793
+ "requestId":str(uuid4()),
794
+ "externalReceiverId":receiver_id,
795
+ "ackRiskError":False,
796
+ "source":"sendmoney_history_chat",
797
+ "socketConnection": "P2P"
798
+ }
799
+ if pochibukuro:
800
+ payload["theme"]="pochibukuro"
801
+
802
+ send=self.session.post(f"https://app4.paypay.ne.jp/p2p/v3/executeP2PSendMoney",headers=self.headers,json=payload,params=self.params,proxies=self.proxy)
803
+ try:
804
+ send=send.json()
805
+ except:
806
+ raise PayPayNetWorkError("日本以外からは接続できません")
807
+
808
+ if send["header"]["resultCode"] == "S0001":
809
+ raise PayPayLoginError(send)
810
+
811
+ if send["header"]["resultCode"] != "S0000":
812
+ raise PayPayError(send)
813
+
814
+ class SendMoney(NamedTuple):
815
+ chat_room_id: str
816
+ order_id: str
817
+ raw: dict
818
+
819
+ chat_room_id=send["payload"]["chatRoomId"]
820
+ order_id=send["payload"]["orderId"]
821
+
822
+ return SendMoney(chat_room_id,order_id,send)
823
+
824
+ def send_message(self,chat_room_id:str,message:str) -> dict:
825
+ if not self.access_token:
826
+ raise PayPayLoginError("まずはログインしてください")
827
+
828
+ if not "sendbird_group_channel_" in chat_room_id:
829
+ chat_room_id="sendbird_group_channel_" + chat_room_id
830
+
831
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
832
+ payload = {
833
+ "channelUrl":chat_room_id,
834
+ "message":message,
835
+ "socketConnection": "P2P"
836
+ }
837
+ send=self.session.post("https://app4.paypay.ne.jp/p2p/v1/sendP2PMessage",headers=self.headers,json=payload,params=self.params,proxies=self.proxy).json()
838
+
839
+ if send["header"]["resultCode"] == "S0001":
840
+ raise PayPayLoginError(send)
841
+
842
+ if send["header"]["resultCode"] != "S0000":
843
+ raise PayPayError(send)
844
+
845
+ return send
846
+
847
+ def create_p2pcode(self,amount:int=None):
848
+ if not self.access_token:
849
+ raise PayPayLoginError("まずはログインしてください")
850
+
851
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
852
+ payload = {
853
+ "amount":None,
854
+ "sessionId":None
855
+ }
856
+ if amount:
857
+ payload["amount"]=amount
858
+ payload["sessionId"]=str(uuid4())
859
+
860
+ create_p2pcode=self.session.post("https://app4.paypay.ne.jp/bff/v1/createP2PCode",headers=self.headers,json=payload,params=self.params,proxies=self.proxy).json()
861
+
862
+ if create_p2pcode["header"]["resultCode"] == "S0001":
863
+ raise PayPayLoginError(create_p2pcode)
864
+
865
+ if create_p2pcode["header"]["resultCode"] != "S0000":
866
+ raise PayPayError(create_p2pcode)
867
+
868
+ class P2PCode(NamedTuple):
869
+ p2pcode: str
870
+ raw: dict
871
+
872
+ p2pcode=create_p2pcode["payload"]["p2pCode"]
873
+
874
+ return P2PCode(p2pcode,create_p2pcode)
875
+
876
+ def get_profile(self):
877
+ if not self.access_token:
878
+ raise PayPayLoginError("まずはログインしてください")
879
+
880
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0",False,"ProfileFragment",0)
881
+ profile=self.session.get("https://app4.paypay.ne.jp/bff/v2/getProfileDisplayInfo",headers=self.headers,params={"includeExternalProfileSync":"true","completedOptionalTasks": "ENABLED_NEARBY_DEALS","payPayLang":"ja"},proxies=self.proxy).json()
882
+
883
+ if profile["header"]["resultCode"] == "S0001":
884
+ raise PayPayLoginError(profile)
885
+
886
+ if profile["header"]["resultCode"] != "S0000":
887
+ raise PayPayError(profile)
888
+
889
+ class Profile(NamedTuple):
890
+ name: str
891
+ external_user_id: str
892
+ icon: str
893
+ raw: dict
894
+
895
+ name=profile["payload"]["userProfile"]["nickName"]
896
+ external_user_id=profile["payload"]["userProfile"]["externalUserId"]
897
+ icon=profile["payload"]["userProfile"]["avatarImageUrl"]
898
+
899
+ return Profile(name,external_user_id,icon,profile)
900
+
901
+ def set_money_priority(self,paypay_money:bool=False) -> dict:
902
+ if not self.access_token:
903
+ raise PayPayLoginError("まずはログインしてください")
904
+
905
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
906
+
907
+ if paypay_money:
908
+ setting={"moneyPriority":"MONEY_FIRST"}
909
+ else:
910
+ setting={"moneyPriority":"MONEY_LITE_FIRST"}
911
+
912
+ smp=self.session.post("https://app4.paypay.ne.jp/p2p/v1/setMoneyPriority",headers=self.headers,json=setting,params={"payPayLang":"ja"},proxies=self.proxy).json()
913
+
914
+ if smp["header"]["resultCode"] == "S0001":
915
+ raise PayPayLoginError(smp)
916
+
917
+ if smp["header"]["resultCode"] != "S0000":
918
+ raise PayPayError(smp)
919
+
920
+ return smp
921
+
922
+ def get_chat_rooms(self,size:int=20,last_message:bool=True):
923
+ if not self.access_token:
924
+ raise PayPayLoginError("まずはログインしてください")
925
+
926
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0.0099999997764826",False,"P2PChatRoomListFragment",0)
927
+ params={
928
+ "pageSize":str(size),
929
+ "customTypes":"P2P_CHAT,P2P_CHAT_INACTIVE,P2P_PUBLIC_GROUP_CHAT,P2P_LINK,P2P_OLD",
930
+ "requiresLastMessage":last_message,
931
+ "socketConnection": "P2P",
932
+ "payPayLang":"ja"
933
+ }
934
+ getchat=self.session.get("https://app4.paypay.ne.jp/p2p/v1/getP2PChatRoomListLite",headers=self.headers,params=params,proxies=self.proxy).json()
935
+
936
+ if getchat["header"]["resultCode"] == "S0001":
937
+ raise PayPayLoginError(getchat)
938
+
939
+ if getchat["header"]["resultCode"] == "S5000":
940
+ raise PayPayError("チャットルームが見つかりませんでした")
941
+
942
+ if getchat["header"]["resultCode"] != "S0000":
943
+ raise PayPayError(getchat)
944
+
945
+ return getchat
946
+
947
+ def get_chat_room_messages(self,chat_room_id:str,prev:int=15,next:int=0,include:bool=False) -> dict:
948
+ if not self.access_token:
949
+ raise PayPayLoginError("まずはログインしてください")
950
+
951
+ if not "sendbird_group_channel_" in chat_room_id:
952
+ chat_room_id="sendbird_group_channel_" + chat_room_id
953
+
954
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0.0099999997764826",False,"P2PChatRoomFragment",0)
955
+ params={
956
+ "chatRoomId":chat_room_id,
957
+ "include":include,
958
+ "prev":str(prev),
959
+ "next":str(next),
960
+ "payPayLang":"ja"
961
+ }
962
+ getchat=self.session.get("https://app4.paypay.ne.jp/bff/v1/getP2PMessageList",headers=self.headers,params=params,proxies=self.proxy).json()
963
+
964
+ if getchat["header"]["resultCode"] == "S0001":
965
+ raise PayPayLoginError(getchat)
966
+
967
+ if getchat["header"]["resultCode"] == "S5000":
968
+ raise PayPayError("チャットルームが見つかりませんでした")
969
+
970
+ if getchat["header"]["resultCode"] != "S0000":
971
+ raise PayPayError(getchat)
972
+
973
+ return getchat
974
+
975
+ def get_point_history(self) -> dict:
976
+ if not self.access_token:
977
+ raise PayPayLoginError("まずはログインしてください")
978
+
979
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
980
+ params = {
981
+ "pageSize": "20",
982
+ "orderTypes": "CASHBACK",
983
+ "paymentMethodTypes": "",
984
+ "signUpCompletedAt": "2021-01-02T10:16:24Z",
985
+ "pointType": "REGULAR",
986
+ "isOverdraftOnly": "false",
987
+ "payPayLang": "ja"
988
+ }
989
+ phistory = self.session.get("https://app4.paypay.ne.jp/bff/v3/getPaymentHistory",headers=self.headers,params=params,proxies=self.proxy).json()
990
+
991
+ if phistory["header"]["resultCode"] == "S0001":
992
+ raise PayPayLoginError(phistory)
993
+
994
+ if phistory["header"]["resultCode"] != "S0000":
995
+ raise PayPayError(phistory)
996
+
997
+ return phistory
998
+
999
+ def search_p2puser(self,user_id:str,size:int=10,is_global:bool=True,order:int=0):
1000
+ if not self.access_token:
1001
+ raise PayPayLoginError("まずはログインしてください")
1002
+
1003
+ self.headers=update_header_baggage(self.headers,sentry_public_key)
1004
+ payload={
1005
+ "searchTerm":user_id,
1006
+ "pageToken":"",
1007
+ "pageSize":size,
1008
+ "isIngressSendMoney":False,
1009
+ "searchTypes":"GLOBAL_SEARCH"
1010
+ }
1011
+ if not is_global:
1012
+ payload["searchTypes"]="FRIEND_AND_CANDIDATE_SEARCH"
1013
+
1014
+ p2puser = self.session.post("https://app4.paypay.ne.jp/p2p/v3/searchP2PUser",headers=self.headers,json=payload,params=self.params,proxies=self.proxy).json()
1015
+ if p2puser["header"]["resultCode"] == "S0001":
1016
+ raise PayPayLoginError(p2puser)
1017
+
1018
+ if p2puser["header"]["resultCode"] != "S0000":
1019
+ if p2puser["error"]["displayErrorResponse"]["description"]=="しばらく時間をおいて、再度お試しください":
1020
+ raise PayPayError("レート制限に達しました")
1021
+
1022
+ raise PayPayError(p2puser)
1023
+
1024
+ if p2puser["payload"]["searchResultEnum"] == "NO_USERS_FOUND":
1025
+ raise PayPayError("ユーザーが見つかりませんでした")
1026
+
1027
+ class P2PUser(NamedTuple):
1028
+ name: str
1029
+ icon: str
1030
+ external_id: str
1031
+ raw: dict
1032
+
1033
+ if is_global:
1034
+ name=p2puser["payload"]["globalSearchResult"]["displayName"]
1035
+ icon=p2puser["payload"]["globalSearchResult"]["photoUrl"]
1036
+ external_id=p2puser["payload"]["globalSearchResult"]["externalId"]
1037
+ else:
1038
+ name=p2puser["payload"]["friendsAndCandidatesSearchResults"]["friends"][order]["displayName"]
1039
+ icon=p2puser["payload"]["friendsAndCandidatesSearchResults"]["friends"][order]["photoUrl"]
1040
+ external_id=p2puser["payload"]["friendsAndCandidatesSearchResults"]["friends"][order]["externalId"]
1041
+
1042
+ return P2PUser(name,icon,external_id,p2puser)
1043
+
1044
+ def initialize_chatroom(self,external_id:str):
1045
+ if not self.access_token:
1046
+ raise PayPayLoginError("まずはログインしてください")
1047
+
1048
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0.0099999997764826",False,"P2PChatRoomFragment",0)
1049
+ payload={
1050
+ "returnChatRoom":True,
1051
+ "shouldCheckMessageForFriendshipAppeal":True,
1052
+ "externalUserId":external_id,
1053
+ "socketConnection": "P2P"
1054
+ }
1055
+ initialize = self.session.post("https://app4.paypay.ne.jp/p2p/v1/initialiseOneToOneAndLinkChatRoom",headers=self.headers,json=payload,params=self.params,proxies=self.proxy).json()
1056
+ if initialize["header"]["resultCode"] == "S0001":
1057
+ raise PayPayLoginError(initialize)
1058
+
1059
+ if initialize["header"]["resultCode"] == "S5000":
1060
+ raise PayPayError("チャットルームが見つかりませんでした")
1061
+
1062
+ if initialize["header"]["resultCode"] != "S0000":
1063
+ raise PayPayError(initialize)
1064
+
1065
+ class InitializeChatRoom(NamedTuple):
1066
+ chatroom_id: str
1067
+ raw: dict
1068
+
1069
+ chatroom_id=initialize["payload"]["chatRoom"]["chatRoomId"]
1070
+
1071
+ return InitializeChatRoom(chatroom_id,initialize)
1072
+
1073
+ def alive(self) -> None:
1074
+ if not self.access_token:
1075
+ raise PayPayLoginError("まずはログインしてください")
1076
+
1077
+ self.headers=update_header_baggage(self.headers,sentry_public_key,"0.0099999997764826",False,"MainActivity",0)
1078
+ alive=self.session.get("https://app4.paypay.ne.jp/bff/v1/getGlobalServiceStatus?payPayLang=en",headers=self.headers,proxies=self.proxy).json()
1079
+ if alive["header"]["resultCode"] == "S0001":
1080
+ raise PayPayLoginError(alive)
1081
+
1082
+ if alive["header"]["resultCode"] != "S0000":
1083
+ raise PayPayError(alive)
1084
+
1085
+ self.session.post("https://app4.paypay.ne.jp/bff/v3/getHomeDisplayInfo?payPayLang=ja",headers=self.headers,json={"excludeMissionBannerInfoFlag": False,"includeBeginnerFlag": False,"includeSkinInfoFlag": False,"networkStatus": "WIFI"},proxies=self.proxy)
1086
+ self.session.get("https://app4.paypay.ne.jp/bff/v1/getSearchBar?payPayLang=ja",headers=self.headers,proxies=self.proxy)
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.1
2
+ Name: PayPaython-mobile
3
+ Version: 2.3
4
+ Summary: An API wrapper for the PayPayAPI
5
+ Home-page: https://github.com/taka-4602/PayPaython-mobile
6
+ Author: taka4602
7
+ Author-email: takaka4602@gmail.com
8
+ Keywords: paypay
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.6
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: requests
15
+ Requires-Dist: pkce
16
+
17
+ # PayPaython-mobile ![icon](https://raw.githubusercontent.com/taka-4602/PayPaython/main/images/1.png)
18
+ Python用のPayPayモバイルAPIラッパー
19
+ ## インストール
20
+ ```py
21
+ pip install paypaython-mobile
22
+ ```
23
+ 必須:requests, pkce
24
+ ## [ ! ] PayPayからのレスポンス集 -> *[PayPayResponce.md](https://github.com/taka-4602/PayPaython/blob/main/PAYPAYRESPONCE.md)*
25
+ PayPay APIを使った時に返されるレスポンスをまとめたドキュメントです
26
+ 返ってきたレスポンスにどんな意味があるか知りたい場合、このドキュメントが役に立つかもしれません
27
+ ## 始める前に確認すること
28
+ ### ログインを3回失敗するとアカウントが一時ロックされる
29
+ PayPayのサポートに連絡することで早く解除してもらえるみたいです (めんどくさいけど)
30
+ ### セッションを作りすぎない
31
+ セッションを大量に作るとアカウント凍結の可能性があるみたいです (モバイルAPIで起こるか不明だし、WebAPIでも僕は未確認)
32
+ ### 日本からしかアクセスできない
33
+ ふつうにブロックされます
34
+ 海外のバーチャルマシンとかを使う場合はプロキシを使いましょう
35
+ ## Let's Go! ([前と同じ](https://github.com/taka-4602/PayPaython?tab=readme-ov-file#lets-go)ノリ)
36
+ #### example.py
37
+ ```py
38
+ from PayPaython_mobile import PayPay
39
+
40
+ paypay=PayPay("080-1234-5678","Unko-1234")#電話番号とパスワードでログインスタート、ハイフンはありでもなしでも。
41
+ url=input("URL?: ")#URLと書いてあるけどIDだけでもOK
42
+ paypay.login(url)#URLなら https://www.paypay.ne.jp/portal/oauth2/l?id=TK4602 をそのままいれる、IDをいれるなら id=の横、TK4602
43
+ print(paypay.access_token)#アクセストークンは90日有効
44
+ print(paypay.refresh_token)
45
+ print(paypay.device_uuid)#デバイスUUIDで登録デバイスを管理してるぽい
46
+ print(paypay.client_uuid)#クライアントUUIDは特に必要ない
47
+ #これでログイン完了、次回からはアクセストークンかデバイスUUIDを入力してログインできる
48
+ #アクセストークンならログイン作業自体をスキップできる
49
+
50
+ paypay=PayPay("080-1234-5678","Unko-1234","登録済みのデバイスUUID",proxy=None)#プロキシはdictでもstrでもOK、str="http://host:port" http://が無くてもOK
51
+ print(paypay.access_token)
52
+ print(paypay.refresh_token)
53
+ #URLを入力する必要はない
54
+
55
+ paypay=PayPay(access_token="アクセストークン")
56
+ #ログインをスキップ
57
+
58
+ paypay.token_refresh("ここにリフレッシュトークン")#アクセストークンは90日経つと失効するので失効したらリフレッシュしよう
59
+ print(paypay.access_token)
60
+ print(paypay.refresh_token)
61
+ #↑ここ2つはリフレッシュ後のものを返すようになる
62
+
63
+ get_profile=paypay.get_profile()#引数なし、プロフィールを取得する
64
+ print(get_profile.name)#ユーザー名
65
+ print(get_profile.external_user_id)#識別のためのユーザーID、自分で決められるやつとは違う
66
+ print(get_profile.icon)#アイコンのURL
67
+
68
+ get_balance=paypay.get_balance()#これも引数なし、PayPay残高を取得する
69
+ print(get_balance.all_balance)#すべての残高
70
+ print(get_balance.useable_balance)#すべての使用可能な残高
71
+ print(get_balance.money_light)#もってるマネーライト
72
+ print(get_balance.money)#もってるマネー
73
+ print(get_balance.points)#もってるポイント
74
+
75
+ print(paypay.get_history(size=20))#支出入の履歴を取得する、size=どれだけ履歴を取得するか、デフォルトは20だったけど少なくもできる
76
+ print(paypay.get_chat_rooms(size=20))#PayPayのDMリストを取得する
77
+ print(paypay.get_chat_room_messages(chat_room_id="sendbird_group_channel_なんとか_なんとか"))#グループIDのDMを取得する sendbird_group_channel_ はなくてもOK
78
+ print(paypay.get_point_history())#ポイントの履歴を取得する
79
+
80
+ paypay.link_check("KT975hvzbH1EulTr")#web=True でWebAPIを使ってリンクを確認できる
81
+ link_info=paypay.link_check("https://pay.paypay.ne.jp/KT975hvzbH1EulTr")#URLそのままでもOK
82
+ print(link_info.amount)#リンクの合計金額
83
+ print(link_info.money_light)#金額のマネーライト分
84
+ print(link_info.money)#金額のマネー分
85
+ print(link_info.has_password)#パスワードがあるなら True
86
+ print(link_info.chat_room_id)#チャットルームID リンク受け取ったらメッセージ送れるあれのID
87
+ print(link_info.status)#PENDING COMPLEATED REJECTED FAILED
88
+ print(link_info.order_id)
89
+
90
+ paypay.link_receive("ここもURL / IDどっちでもOK","必要ならパスワード 4602",link_info=link_info)#リンク受け取り、link_infoにリンクのdictをぶちこむとリンクチェックをスキップする
91
+ paypay.link_reject("ここもURL / IDどっちでもOK",link_info=link_info)#リンクを辞退する、link_infoがないならチェックリンクするのでどっちでもいい
92
+ paypay.link_cancel("ここもURL / IDどっちでもOK",link_info=link_info)#PayPayやっとリンクキャンセルできるようになった
93
+
94
+ create_link=paypay.create_link(amount=100,passcode="4602")#送金リンク作成、金額と必要ならパスワード
95
+ print(create_link.link)#↑で作ったURL
96
+ print(create_link.chat_room_id)#↑で作ったリンクのチャットルームID
97
+
98
+ create_p2pcode=paypay.create_p2pcode()#自分に送金してもらうためのQRコードのリンク、amount=intでQRコードの値段を設定
99
+ print(create_p2pcode.p2pcode)#↑で作ったURL
100
+
101
+ send_money=paypay.send_money(amount=100,receiver_id="受取人のexternal_id")
102
+ print(send_money.chat_room_id)#send_moneyにもチャットルームIDを追加
103
+ paypay.send_message(chat_room_id="DMのID",message="100円くれてありがとう!")#取得したchat_room_idをそのまま入れたり、PayPayのDMを自動化できる、商用してる人なら "お買い上げありがとうございます。" 的な
104
+
105
+ paypay.set_money_priority(paypay_money=False)#PayPayで送る残高の優先度を変更する、Falseでマネーライト優先、Trueでマネー優先に設定
106
+
107
+ search_p2puser=paypay.search_p2puser(user_id="ユーザーID")#ユーザーが決められるPayPayIDでユーザー検索ができる、グローバルサーチはすぐにレート制限に入る
108
+ print(search_p2puser.name)#見つかったユーザーの表示名
109
+ print(search_p2puser.icon)#見つかったユーザーのアイコン
110
+ print(search_p2puser.external_id)#見つかったユーザーのExternalID
111
+
112
+ search_p2puser=paypay.search_p2puser(user_id="表示名",is_global=False,order=0)#is_grobalをFalseにしてフレンドなら表示名でExternalIDを取得できる、orderはユーザー名が被った時何番目のユーザーの情報を取得するか、リストだから1番上は0
113
+ print(search_p2puser.icon)#見つかったユーザーのアイコン
114
+ print(search_p2puser.external_id)#見つかったユーザーのExternalID
115
+
116
+ initialize_chatroom=paypay.initialize_chatroom("ExternalID")#ExternalIDを使ってDM送信用のチャットルームIDを取得できる
117
+ print(initialize_chatroom.chatroom_id)#見つかったユーザーのチャットルームID
118
+ ```
119
+ WebAPIに比べてすごく長い、でも機能はたくさん
120
+ #コメントで使い方は書いてるしそれが全部
121
+ ### dictを見る必要がなくなった
122
+ 1部 (get_histoyなどリストで返ってくるものとか) 以外、よく使うものは変数にまとめたのでdict探し地獄はマシになったと思います
123
+ それでもすべてのメソッドはdictを返すようになっているので返り値全文を見たい場合は```paypay.なんとか().raw```で見ることができます
124
+ ```.raw```がない場合はそもそもそのメソッドの返り値がそのままdictということです
125
+ ## もう少し知る
126
+ ### ログインについて
127
+ WebAPIは電話番号、パスワード、UUID(Client_UUID)、アクセストークンが1セットでしたがモバイルAPIも根本は同じで電話番号、パスワード、Device_UUID、Client_UUID、アクセストークン、リフレッシュトークンが1セットです
128
+ ログインもアクセストークンをヘッダーにつけることでスキップすることができます
129
+ 電話番号、パスワード、登録済みDevice_UUIDを使うとワンタイムURLなしでログインできます
130
+ ```py
131
+ paypay=PayPay("080-1234-5678","Unko-1234","登録済みのデバイスUUID",proxy=None)
132
+ print(paypay.access_token)
133
+ print(paypay.refresh_token)
134
+ #URLを入力する必要はない
135
+ ```
136
+ ###### 電話番号のハイフンはあってもなくてもOK
137
+ どうやらクライアントUUIDは常にテキトーでいいみたいです
138
+ アクセストークンがヘッダーについてる場合、デバイスUUIDもテキトーでいいみたいです
139
+ ```py
140
+ paypay=PayPay(access_token="アクセストークン")
141
+ #ログインをスキップ
142
+ ```
143
+ ###### アクセストークンは90日間有効みたいで、WebAPIの1080倍長持ち!
144
+ ###### そういえばいつのまにか4桁のOTPは廃止になった
145
+ ### ログインのリフレッシュ
146
+ 上記にあるように電話番号、パスワード、デバイスUUIDでログインすることでワンタイムURLなしにログインをリフレッシュできるけど、```token_refresh```を使う方がスマート
147
+ ```py
148
+ paypay.token_refresh("ここにリフレッシュトークン")#アクセストークンは90日で失効するので失効したらリフレッシュしよう
149
+ print(paypay.access_token)
150
+ print(paypay.refresh_token)
151
+ #↑ここ2つはリフレッシュ後のものを返すようになる
152
+ ```
153
+ ログインをファイルとかに保存する場合はどっちも保存しておいたほうがベター
154
+ ### PayPayのDM
155
+ なぜか自分に送れるし成功って言われる (送金 / 受け取り履歴のところにメッセージが送られる)
156
+ リンクチェックをした時にDM送る用のチャットルームIDが返ってくる
157
+ 商用してる人は買われたものの詳細を送ったり、単に "ありがとうございました" だけでも需要はありそう
158
+ IDは```sendbird_group_channel_なんとか_なんとか```の形式だけど```send_message```を使う時の引数にする場合```sendbird_group_channel_```の部分はなくてもOK
159
+ #### もう少し効率化
160
+ グループチャンネルIDがリンクチェック時しかわからないのはめんどくさい、検索を組み合わせて使えば効率化できます
161
+ ##### PayPayのユーザーIDで検索する場合
162
+ ```py
163
+ search_p2puser=paypay.search_p2puser(user_id="ユーザーID")#ユーザーが決められるPayPayIDでユーザー検索ができる、グローバルサーチはすぐにレート制限に入る
164
+ paypay.send_message(search_p2puser.user_external_id,"はろー")
165
+ ```
166
+ ##### PayPayの表示名で検索する場合
167
+ ```py
168
+ search_p2puser=paypay.search_p2puser(user_id="表示名",is_global=False,order=0)
169
+ initialize_chatroom=paypay.initialize_chatroom(search_p2puser.external_id)
170
+ paypay.send_message(initialize_chatroom.chatroom_id,"はろー")
171
+ ```
172
+ ユーザーID検索はすぐにレート制限にかかるので、DM送るのはけっきょくリンク受け取り時しかない...
173
+ 表示名でフレンド検索する場合は上記に該当せず、逆にフレンド検索にはPayPayIDが使えない (NotFoundが返される)
174
+ #### もちろんDMではなく直接送金もできる
175
+ ```py
176
+ search_p2puser=paypay.search_p2puser("たか",is_global=False)
177
+ send_money=paypay.send_money(100,search_p2puser.external_id)
178
+ ```
179
+ DM送信の時と違ってExternalIDだけで送金できるからこっちの方が簡単
180
+ ### PayPayエラー
181
+ 受け取りや辞退の時に既に処理済みのリンクを投げるとエラーになるようにしています
182
+ 無効なリンクを処理しようとしてもエラーになります
183
+ また ログイン作業の失敗や S0001 : アクセストークンが取り消されました が返ってきた場合、PayPayLoginError (インポートできます) がraiseされます
184
+ 判別用に使ってください
185
+ ### 備考
186
+ どうやらPayPayのモバイルAPIはBot検知が存在するらしい (検知されるとBotがログアウトされる)
187
+ いろいろ試した結果、無駄なリクエストを送信することで回避できました (ユーザーがアプリケーションを操作してたら当然大量のリクエストをしてるから、それをBotで真似る)
188
+ このモジュールにも無駄リクエスト機能を追加しました
189
+ ```py
190
+ paypay.alive() #引数も返り値もなし
191
+ ```
192
+ Botは効率が良すぎる...
193
+ ### 古いバージョン
194
+ 古いバージョンはPayPayアプリのバージョンをiOSStoreから取得してユーザーエージェントを変えていましたが、どうやらPayPayの仕様変更のせいでPayPayアプリが新しいのに古いリクエストを使ってるのがバレるとBotがログアウトされるみたいです
195
+ 1.xのままその問題を解決したいなら```pip install paypaython-mobile==0.14.8```を使ってください (ユーザーエージェントが固定される)
196
+ 最新バージョンをユーザーエージェントにしてもなにもいいことがないので、新しくしたPayPaython-mobileは固定にしました
197
+ 古いバージョンのドキュメントはここから -> [README_old.md](https://github.com/taka-4602/PayPaython-mobile/blob/main/README_old.md)
198
+ ### 余談
199
+ 久しぶりに中身を大幅に更新しました、でもユーザー目線だと違いがわかりにくい **だけど1.xと2.xには互換性がない**
200
+ バージョンが1.xを飛んで2になって、本当に無駄だった機能を消して、リクエストの内容がだいぶかわった
201
+ ヘッダーがキモくなった原因の1つのセントリートレースはログの監視用だから無くても動くけど、PayPayのことだから凍結されないか心配なのでつけておいたほうが良いはず
202
+ 端末の向き(xyz?)はなんで収集するようになったか全然分からない…
203
+ ###### ~~ユーザーエージェントはiPhone8 (iOS 16.7.5) トラフィック確認に使った端末がiPhone8だから~~
204
+ ###### ~~実機はiOS 14.8だけどユーザーエージェントは16.7.5に変えている (というかPayPayは最近iOS 14のサポートを終了したみたい...)~~
205
+ ユーザーエージェントがiPhone8からKDDI版Samsung S9 (SCV38)に変更されました (iOS 14.8のPayPayアップデートはとっくに切られた)
206
+ iOS 14.8はAndroid 10より2年も新しいのにPayPayに限らずほとんどのアプリが切られてしまった... iOSは寿命が短くて困る... (S9はOneUI 2.1 / Android 10)
207
+ ## コンタクト
208
+ Discord サーバー / https://discord.gg/aSyaAK7Ktm
209
+ Discord ユーザー名 / .taka.
@@ -0,0 +1,6 @@
1
+ PayPaython_mobile/__init__.py,sha256=QPHBT5ZpK-uJkiPrfjZGpBV4lxv53VUUEl2bmmkPSf8,169
2
+ PayPaython_mobile/main.py,sha256=fZ-psd7H-WZ3M1L8Pcju9doCIgPoiTXRIqHjhkCvIiU,49173
3
+ PayPaython_mobile-2.3.dist-info/METADATA,sha256=1_667XAxy8miEp3a1z54hC53uIJbWzK774NXN26HKgA,15760
4
+ PayPaython_mobile-2.3.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
5
+ PayPaython_mobile-2.3.dist-info/top_level.txt,sha256=rCyfWL7CPVFjhVDDC2X8qanKoG8Cn5F0D7rfWCL7DDk,18
6
+ PayPaython_mobile-2.3.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.45.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ PayPaython_mobile