fix: correct ticket charge API body, logout type, and login/logout flow
- Fix upsertUserChargeLog: use separate userCharge + userChargelog objects matching Lionheart reference, validDate 90 days at 4AM - Fix userLogout: use LogoutType.Logout = 1 (was incorrectly changed to 0) - Fix loginDateTime tracking: userLoginFull now returns the exact timestamp sent to server, ensuring logout uses matching value - Fix home page logout: call UserLogoutApi before navigating back - Add force-logout for stale sessions before ticket flow login - Add comprehensive debug logging across entire ticket flow Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -21,11 +21,13 @@ class UserLoginResult {
|
||||
final String token;
|
||||
final int loginId;
|
||||
final String lastLoginDate;
|
||||
final int loginDateTime;
|
||||
|
||||
const UserLoginResult({
|
||||
required this.token,
|
||||
required this.loginId,
|
||||
required this.lastLoginDate,
|
||||
required this.loginDateTime,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -109,6 +111,25 @@ class TitleApiService {
|
||||
final baseUrl = _normalizeUrl(_config.titleServerUrl);
|
||||
final url = Uri.parse('$baseUrl/$hash');
|
||||
|
||||
// ignore: avoid_print
|
||||
print('══════════════════════════════════════');
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] >>> REQUEST >>>');
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] URL: $url');
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] userId: $userId');
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] hash: $hash');
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] body (encrypted): ${body.length} bytes');
|
||||
// ignore: avoid_print
|
||||
final packetStr = const JsonEncoder.withIndent(' ').convert(packet);
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] packet (plain):');
|
||||
// ignore: avoid_print
|
||||
print(packetStr);
|
||||
|
||||
final response = await http
|
||||
.post(
|
||||
url,
|
||||
@@ -125,6 +146,11 @@ class TitleApiService {
|
||||
)
|
||||
.timeout(const Duration(seconds: 15));
|
||||
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] HTTP status: ${response.statusCode}');
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] response bytes: ${response.bodyBytes.length}');
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw TitleApiException('$apiName returned ${response.statusCode}');
|
||||
}
|
||||
@@ -132,9 +158,11 @@ class TitleApiService {
|
||||
final json = _processResponseBody(response.bodyBytes);
|
||||
final raw = const JsonEncoder.withIndent(' ').convert(json);
|
||||
// ignore: avoid_print
|
||||
print('[$apiName] === RESPONSE ===');
|
||||
print('[$apiName] <<< RESPONSE <<<');
|
||||
// ignore: avoid_print
|
||||
print(raw);
|
||||
// ignore: avoid_print
|
||||
print('══════════════════════════════════════');
|
||||
|
||||
return json;
|
||||
}
|
||||
@@ -380,9 +408,9 @@ class TitleApiService {
|
||||
|
||||
final json = await _callApi(apiName, packet, userId);
|
||||
|
||||
final errorId = json['errorId'] as int? ?? -1;
|
||||
if (errorId != 0) {
|
||||
throw TitleApiException('UserLoginApi errorId=$errorId');
|
||||
final returnCode = json['returnCode'] as int? ?? -1;
|
||||
if (returnCode != 1) {
|
||||
throw TitleApiException('UserLoginApi returnCode=$returnCode');
|
||||
}
|
||||
|
||||
final loginId = (json['loginId'] as num?)?.toInt() ?? 0;
|
||||
@@ -393,6 +421,7 @@ class TitleApiService {
|
||||
token: newToken,
|
||||
loginId: loginId,
|
||||
lastLoginDate: lastLoginDate,
|
||||
loginDateTime: now,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -411,13 +440,55 @@ class TitleApiService {
|
||||
'placeId': _config.placeId,
|
||||
'clientId': _config.clientId,
|
||||
'loginDateTime': loginDateTime,
|
||||
'type': 1,
|
||||
'type': 1, // LogoutType.Logout
|
||||
};
|
||||
|
||||
final json = await _callApi(apiName, packet, userId);
|
||||
final errorId = json['errorId'] as int? ?? -1;
|
||||
if (errorId != 0) {
|
||||
throw TitleApiException('UserLogoutApi errorId=$errorId');
|
||||
final returnCode = json['returnCode'] as int? ?? -1;
|
||||
if (returnCode != 1) {
|
||||
throw TitleApiException('UserLogoutApi returnCode=$returnCode');
|
||||
}
|
||||
}
|
||||
|
||||
// ---- UpsertUserChargeLog (使用功能票) ----
|
||||
// Reference: Lionheart user.ts charge() method
|
||||
|
||||
Future<void> upsertUserChargeLog({
|
||||
required int userId,
|
||||
required int ticketId,
|
||||
required String loginDate,
|
||||
required int playerRating,
|
||||
int price = 0,
|
||||
}) async {
|
||||
const apiName = 'UpsertUserChargelogApi';
|
||||
final now = DateTime.now();
|
||||
// validDate defaults to purchaseDate + 90 days at 4:00 AM
|
||||
final validDate = DateTime(now.year, now.month, now.day, 4, 0, 0)
|
||||
.add(const Duration(days: 90));
|
||||
final purchaseDateStr = _formatDateTime(now);
|
||||
|
||||
final packet = {
|
||||
'userId': userId,
|
||||
'userCharge': {
|
||||
'chargeId': ticketId,
|
||||
'stock': 0,
|
||||
'purchaseDate': purchaseDateStr,
|
||||
'validDate': _formatDateTime(validDate),
|
||||
},
|
||||
'userChargelog': {
|
||||
'chargeId': ticketId,
|
||||
'clientId': _config.clientId,
|
||||
'regionId': _config.regionId,
|
||||
'placeId': _config.placeId,
|
||||
'price': price,
|
||||
'purchaseDate': purchaseDateStr,
|
||||
},
|
||||
};
|
||||
|
||||
final json = await _callApi(apiName, packet, userId);
|
||||
final returnCode = json['returnCode'] as int? ?? -1;
|
||||
if (returnCode != 1) {
|
||||
throw TitleApiException('UpsertUserChargelogApi returnCode=$returnCode');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,6 +499,7 @@ class TitleApiService {
|
||||
required int loginId,
|
||||
required Map<String, dynamic> musicData,
|
||||
required Map<String, dynamic> userData,
|
||||
int? ticketId,
|
||||
}) async {
|
||||
const apiName = 'UploadUserPlaylogListApi';
|
||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
@@ -435,14 +507,14 @@ class TitleApiService {
|
||||
final packet = {
|
||||
'userId': userId,
|
||||
'userPlaylogList': [
|
||||
_buildPlaylogEntry(userId, loginId, now, musicData, userData),
|
||||
_buildPlaylogEntry(userId, loginId, now, musicData, userData, ticketId: ticketId),
|
||||
],
|
||||
};
|
||||
|
||||
final json = await _callApi(apiName, packet, userId);
|
||||
final errorId = json['errorId'] as int? ?? -1;
|
||||
if (errorId != 0) {
|
||||
throw TitleApiException('UploadUserPlaylogListApi errorId=$errorId');
|
||||
final returnCode = json['returnCode'] as int? ?? -1;
|
||||
if (returnCode != 1) {
|
||||
throw TitleApiException('UploadUserPlaylogListApi returnCode=$returnCode');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,8 +523,9 @@ class TitleApiService {
|
||||
int loginId,
|
||||
int timestamp,
|
||||
Map<String, dynamic> musicData,
|
||||
Map<String, dynamic> userData,
|
||||
) {
|
||||
Map<String, dynamic> userData, {
|
||||
int? ticketId,
|
||||
}) {
|
||||
final user = userData['userData'] as Map<String, dynamic>? ?? {};
|
||||
final charaSlot = (user['charaSlot'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
@@ -574,12 +647,13 @@ class TitleApiService {
|
||||
|
||||
// ---- UpsertUserAll ----
|
||||
|
||||
Future<void> upsertUserAll({
|
||||
Future<Map<String, dynamic>> upsertUserAll({
|
||||
required int userId,
|
||||
required int loginId,
|
||||
required String loginDate,
|
||||
required Map<String, dynamic> musicData,
|
||||
required List<Map<String, dynamic>> generalUserInfo,
|
||||
int? ticketId,
|
||||
}) async {
|
||||
const apiName = 'UpsertUserAllApi';
|
||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
@@ -601,7 +675,7 @@ class TitleApiService {
|
||||
'isFreePlay': false,
|
||||
'loginDateTime': now,
|
||||
'userPlaylogList': [
|
||||
_buildPlaylogEntry(userId, loginId, now, musicData, userData),
|
||||
_buildPlaylogEntry(userId, loginId, now, musicData, userData, ticketId: ticketId),
|
||||
],
|
||||
'upsertUserAll': {
|
||||
'userData': [
|
||||
@@ -722,7 +796,7 @@ class TitleApiService {
|
||||
'version': user['lastRomVersion'] ?? 0,
|
||||
'playDate': _formatDateTime(DateTime.now()),
|
||||
'playMode': 0,
|
||||
'useTicketId': -1,
|
||||
'useTicketId': ticketId ?? -1,
|
||||
'playCredit': 1,
|
||||
'playTrack': 1,
|
||||
'clientId': _config.clientId,
|
||||
@@ -764,10 +838,11 @@ class TitleApiService {
|
||||
};
|
||||
|
||||
final json = await _callApi(apiName, packet, userId);
|
||||
final errorId = json['errorId'] as int? ?? -1;
|
||||
if (errorId != 0) {
|
||||
throw TitleApiException('UpsertUserAllApi errorId=$errorId');
|
||||
final returnCode = json['returnCode'] as int? ?? -1;
|
||||
if (returnCode != 1) {
|
||||
throw TitleApiException('UpsertUserAllApi returnCode=$returnCode');
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _buildMissionDataList(
|
||||
|
||||
Reference in New Issue
Block a user