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:
hailin 2025-12-09 02:10:57 -08:00
parent d0487c4a7e
commit 26ecb39476
3 changed files with 65 additions and 22 deletions

View File

@ -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'; //
// //

View File

@ -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),

View File

@ -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;