fix(mobile-app): pass referral code to backend during account creation
- Add inviterReferralCode storage key for temporary referral code storage - Save referral code in guide page before navigating to onboarding - Read and pass referral code to createAccount API in onboarding page - Clear temp storage after successful account creation - Improve QR code extraction to prioritize query params (?ref=, ?code=) This fixes a critical bug where referral relationships were never being established because the frontend wasn't passing the referral code to the backend. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d0487c4a7e
commit
26ecb39476
|
|
@ -8,6 +8,7 @@ class StorageKeys {
|
||||||
static const String avatarUrl = 'avatar_url'; // 用户上传的头像URL
|
static const String avatarUrl = 'avatar_url'; // 用户上传的头像URL
|
||||||
static const String referralCode = 'referral_code'; // 推荐码
|
static const String referralCode = 'referral_code'; // 推荐码
|
||||||
static const String inviterSequence = 'inviter_sequence'; // 推荐人序列号
|
static const String inviterSequence = 'inviter_sequence'; // 推荐人序列号
|
||||||
|
static const String inviterReferralCode = 'inviter_referral_code'; // 邀请人推荐码(注册前临时存储)
|
||||||
static const String isAccountCreated = 'is_account_created'; // 账号是否已创建
|
static const String isAccountCreated = 'is_account_created'; // 账号是否已创建
|
||||||
|
|
||||||
// 钱包信息
|
// 钱包信息
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
import '../../../../core/di/injection_container.dart';
|
||||||
|
import '../../../../core/storage/storage_keys.dart';
|
||||||
import '../../../../routes/route_paths.dart';
|
import '../../../../routes/route_paths.dart';
|
||||||
import '../providers/auth_provider.dart';
|
import '../providers/auth_provider.dart';
|
||||||
|
|
||||||
|
|
@ -247,7 +249,7 @@ class _GuidePageState extends ConsumerState<GuidePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 欢迎加入页面内容 (第5页)
|
/// 欢迎加入页面内容 (第5页)
|
||||||
class _WelcomePageContent extends StatefulWidget {
|
class _WelcomePageContent extends ConsumerStatefulWidget {
|
||||||
final VoidCallback onNext;
|
final VoidCallback onNext;
|
||||||
final VoidCallback onExit;
|
final VoidCallback onExit;
|
||||||
|
|
||||||
|
|
@ -257,10 +259,10 @@ class _WelcomePageContent extends StatefulWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_WelcomePageContent> createState() => _WelcomePageContentState();
|
ConsumerState<_WelcomePageContent> createState() => _WelcomePageContentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _WelcomePageContentState extends State<_WelcomePageContent> {
|
class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
|
||||||
bool _hasReferrer = true;
|
bool _hasReferrer = true;
|
||||||
final TextEditingController _referralCodeController = TextEditingController();
|
final TextEditingController _referralCodeController = TextEditingController();
|
||||||
|
|
||||||
|
|
@ -313,6 +315,24 @@ class _WelcomePageContentState extends State<_WelcomePageContent> {
|
||||||
return _isValidReferralCode(_referralCodeController.text);
|
return _isValidReferralCode(_referralCodeController.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 保存推荐码并继续下一步
|
||||||
|
Future<void> _saveReferralCodeAndProceed() async {
|
||||||
|
if (!_canProceed) return;
|
||||||
|
|
||||||
|
// 如果有推荐人且推荐码有效,保存到本地存储
|
||||||
|
if (_hasReferrer && _referralCodeController.text.trim().isNotEmpty) {
|
||||||
|
final secureStorage = ref.read(secureStorageProvider);
|
||||||
|
await secureStorage.write(
|
||||||
|
key: StorageKeys.inviterReferralCode,
|
||||||
|
value: _referralCodeController.text.trim(),
|
||||||
|
);
|
||||||
|
debugPrint('[GuidePage] 保存邀请人推荐码: ${_referralCodeController.text.trim()}');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用下一步回调
|
||||||
|
widget.onNext();
|
||||||
|
}
|
||||||
|
|
||||||
/// 打开二维码扫描页面
|
/// 打开二维码扫描页面
|
||||||
Future<void> _openQrScanner() async {
|
Future<void> _openQrScanner() async {
|
||||||
final result = await Navigator.of(context).push<String>(
|
final result = await Navigator.of(context).push<String>(
|
||||||
|
|
@ -332,7 +352,8 @@ class _WelcomePageContentState extends State<_WelcomePageContent> {
|
||||||
|
|
||||||
/// 从扫描结果中提取推荐码
|
/// 从扫描结果中提取推荐码
|
||||||
/// 支持以下格式:
|
/// 支持以下格式:
|
||||||
/// - 完整 URL: https://app.rwadurian.com/r/ABC123 -> ABC123
|
/// - 查询参数: https://app.rwadurian.com/share?ref=ABC123 -> ABC123
|
||||||
|
/// - 路径格式: https://app.rwadurian.com/r/ABC123 -> ABC123
|
||||||
/// - 短链: https://rwa.link/ABC123 -> ABC123
|
/// - 短链: https://rwa.link/ABC123 -> ABC123
|
||||||
/// - 纯推荐码: ABC123 -> ABC123
|
/// - 纯推荐码: ABC123 -> ABC123
|
||||||
String _extractReferralCode(String scannedData) {
|
String _extractReferralCode(String scannedData) {
|
||||||
|
|
@ -344,27 +365,32 @@ class _WelcomePageContentState extends State<_WelcomePageContent> {
|
||||||
try {
|
try {
|
||||||
final uri = Uri.parse(data);
|
final uri = Uri.parse(data);
|
||||||
|
|
||||||
// 尝试从路径中提取 (如 /r/ABC123 或 /ABC123)
|
// 优先从查询参数中提取 (如 ?ref=ABC123 或 ?code=ABC123)
|
||||||
final pathSegments = uri.pathSegments;
|
|
||||||
if (pathSegments.isNotEmpty) {
|
|
||||||
// 取最后一个路径段作为推荐码
|
|
||||||
final lastSegment = pathSegments.last;
|
|
||||||
if (lastSegment.isNotEmpty) {
|
|
||||||
return lastSegment;
|
|
||||||
}
|
|
||||||
// 如果最后一段是 'r',取倒数第二段
|
|
||||||
if (pathSegments.length >= 2 && lastSegment == 'r') {
|
|
||||||
return pathSegments[pathSegments.length - 2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试从查询参数中提取 (如 ?ref=ABC123 或 ?code=ABC123)
|
|
||||||
final refCode = uri.queryParameters['ref'] ??
|
final refCode = uri.queryParameters['ref'] ??
|
||||||
uri.queryParameters['code'] ??
|
uri.queryParameters['code'] ??
|
||||||
uri.queryParameters['referral'];
|
uri.queryParameters['referral'];
|
||||||
if (refCode != null && refCode.isNotEmpty) {
|
if (refCode != null && refCode.isNotEmpty) {
|
||||||
return refCode;
|
return refCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 其次从路径中提取 (如 /r/ABC123 或 /ABC123)
|
||||||
|
final pathSegments = uri.pathSegments;
|
||||||
|
if (pathSegments.isNotEmpty) {
|
||||||
|
// 取最后一个路径段作为推荐码
|
||||||
|
final lastSegment = pathSegments.last;
|
||||||
|
// 排除常见的非推荐码路径段
|
||||||
|
const excludedSegments = ['share', 'invite', 'r', 'ref', 'referral'];
|
||||||
|
if (lastSegment.isNotEmpty && !excludedSegments.contains(lastSegment.toLowerCase())) {
|
||||||
|
return lastSegment;
|
||||||
|
}
|
||||||
|
// 如果最后一段是排除项且有倒数第二段,检查倒数第二段
|
||||||
|
if (pathSegments.length >= 2) {
|
||||||
|
final secondLastSegment = pathSegments[pathSegments.length - 2];
|
||||||
|
if (secondLastSegment.isNotEmpty && !excludedSegments.contains(secondLastSegment.toLowerCase())) {
|
||||||
|
return secondLastSegment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// URL 解析失败,返回原始数据
|
// URL 解析失败,返回原始数据
|
||||||
debugPrint('URL 解析失败: $e');
|
debugPrint('URL 解析失败: $e');
|
||||||
|
|
@ -435,7 +461,7 @@ class _WelcomePageContentState extends State<_WelcomePageContent> {
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
// 下一步按钮
|
// 下一步按钮
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: _canProceed ? widget.onNext : null,
|
onTap: _canProceed ? _saveReferralCodeAndProceed : null,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: EdgeInsets.symmetric(vertical: 16.h),
|
padding: EdgeInsets.symmetric(vertical: 16.h),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
|
||||||
import '../../../../routes/route_paths.dart';
|
import '../../../../routes/route_paths.dart';
|
||||||
import '../../../../routes/app_router.dart';
|
import '../../../../routes/app_router.dart';
|
||||||
import '../../../../core/di/injection_container.dart';
|
import '../../../../core/di/injection_container.dart';
|
||||||
|
import '../../../../core/storage/storage_keys.dart';
|
||||||
|
|
||||||
/// 创建账号页面 - 用户首次进入应用时的引导页面
|
/// 创建账号页面 - 用户首次进入应用时的引导页面
|
||||||
/// 提供创建钱包和导入助记词两种选项
|
/// 提供创建钱包和导入助记词两种选项
|
||||||
|
|
@ -106,14 +107,29 @@ class _OnboardingPageState extends ConsumerState<OnboardingPage> {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取 AccountService
|
// 获取 AccountService 和 SecureStorage
|
||||||
final accountService = ref.read(accountServiceProvider);
|
final accountService = ref.read(accountServiceProvider);
|
||||||
|
final secureStorage = ref.read(secureStorageProvider);
|
||||||
|
|
||||||
|
// 读取邀请人推荐码(从向导页保存的)
|
||||||
|
final inviterReferralCode = await secureStorage.read(
|
||||||
|
key: StorageKeys.inviterReferralCode,
|
||||||
|
);
|
||||||
|
debugPrint('[OnboardingPage] _createAccount - 邀请人推荐码: ${inviterReferralCode ?? "无"}');
|
||||||
|
|
||||||
// 调用后端 API 创建账号
|
// 调用后端 API 创建账号
|
||||||
debugPrint('[OnboardingPage] _createAccount - 调用 accountService.createAccount()');
|
debugPrint('[OnboardingPage] _createAccount - 调用 accountService.createAccount()');
|
||||||
final response = await accountService.createAccount();
|
final response = await accountService.createAccount(
|
||||||
|
inviterReferralCode: inviterReferralCode,
|
||||||
|
);
|
||||||
debugPrint('[OnboardingPage] _createAccount - 成功! 序列号=${response.userSerialNum}, 用户名=${response.username}');
|
debugPrint('[OnboardingPage] _createAccount - 成功! 序列号=${response.userSerialNum}, 用户名=${response.username}');
|
||||||
|
|
||||||
|
// 创建成功后清除临时存储的邀请人推荐码
|
||||||
|
if (inviterReferralCode != null) {
|
||||||
|
await secureStorage.delete(key: StorageKeys.inviterReferralCode);
|
||||||
|
debugPrint('[OnboardingPage] _createAccount - 已清除临时邀请人推荐码');
|
||||||
|
}
|
||||||
|
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
debugPrint('[OnboardingPage] _createAccount - Widget已卸载,忽略响应');
|
debugPrint('[OnboardingPage] _createAccount - Widget已卸载,忽略响应');
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue