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,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 
|
|
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 @@
|
|
|
1
|
+
PayPaython_mobile
|