feat: 实现 iOS Apple Pay 内购支付功能
前端: - 集成 in_app_purchase 插件,实现 IAP 支付流程 - 添加支付模块 (payments/) 处理产品获取、购买、验证 - 积分中心页面集成 Apple Pay 购买入口 - 设置页面重构: 关于/隐私/协议直接展示,删除 legal_center 子页面 - 修复欢迎引导页滚动检测阈值问题 - 修复解卦结果页 iOS 侧滑返回手势被阻止的问题 - 邀请码绑定按钮临时禁用(待后端实现) 后端: - 新增 apple_iap_transactions 表记录交易 - 实现 Apple 服务器端验证 (App Store Server API) - 支付成功后自动发放积分 - 支持 Sandbox/Production 环境切换 - 添加退款处理和交易状态机 协议: - 更新积分流水协议,支持 purchase/refund 类型 - 新增 PAYMENT_* 错误码
This commit is contained in:
@@ -27,6 +27,7 @@ class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({
|
||||
super.key,
|
||||
required this.account,
|
||||
required this.userId,
|
||||
required this.sessionStore,
|
||||
required this.currentLocale,
|
||||
required this.profileSettings,
|
||||
@@ -43,9 +44,11 @@ class HomeScreen extends StatefulWidget {
|
||||
required this.onDeleteHistorySession,
|
||||
required this.onLogout,
|
||||
required this.onDeleteAccount,
|
||||
required this.onBalanceChanged,
|
||||
});
|
||||
|
||||
final String account;
|
||||
final String userId;
|
||||
final SessionStore sessionStore;
|
||||
final Locale currentLocale;
|
||||
final ProfileSettingsV1 profileSettings;
|
||||
@@ -56,15 +59,16 @@ class HomeScreen extends StatefulWidget {
|
||||
final NotificationRepository notificationRepository;
|
||||
final Future<void> Function(String languageTag) onLocaleChanged;
|
||||
final Future<void> Function(ProfileSettingsV1 settings)
|
||||
onProfileSettingsChanged;
|
||||
onProfileSettingsChanged;
|
||||
final Future<ProfileSettingsV1> Function(ProfileSettingsV1 updated)
|
||||
onSaveProfile;
|
||||
onSaveProfile;
|
||||
final Future<ProfileSettingsV1> Function(String filePath) onUploadAvatar;
|
||||
final Future<void> Function(DivinationResultData result)
|
||||
onDivinationCompleted;
|
||||
onDivinationCompleted;
|
||||
final Future<void> Function(String threadId) onDeleteHistorySession;
|
||||
final Future<void> Function() onLogout;
|
||||
final Future<void> Function() onDeleteAccount;
|
||||
final void Function(int newBalance) onBalanceChanged;
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
@@ -132,6 +136,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
),
|
||||
_ProfileTab(
|
||||
account: widget.account,
|
||||
userId: widget.userId,
|
||||
settings: widget.profileSettings,
|
||||
coinBalance: widget.coinBalance,
|
||||
inviteRepository: _inviteRepository,
|
||||
@@ -142,6 +147,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
onUploadAvatar: widget.onUploadAvatar,
|
||||
onLogout: widget.onLogout,
|
||||
onDeleteAccount: widget.onDeleteAccount,
|
||||
onBalanceChanged: widget.onBalanceChanged,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -561,6 +567,7 @@ class _DivinationHistoryScreenState extends State<DivinationHistoryScreen> {
|
||||
class _ProfileTab extends StatelessWidget {
|
||||
const _ProfileTab({
|
||||
required this.account,
|
||||
required this.userId,
|
||||
required this.settings,
|
||||
required this.coinBalance,
|
||||
required this.inviteRepository,
|
||||
@@ -571,9 +578,11 @@ class _ProfileTab extends StatelessWidget {
|
||||
required this.onUploadAvatar,
|
||||
required this.onLogout,
|
||||
required this.onDeleteAccount,
|
||||
required this.onBalanceChanged,
|
||||
});
|
||||
|
||||
final String account;
|
||||
final String userId;
|
||||
final ProfileSettingsV1 settings;
|
||||
final int coinBalance;
|
||||
final InviteRepository inviteRepository;
|
||||
@@ -581,15 +590,17 @@ class _ProfileTab extends StatelessWidget {
|
||||
final Future<void> Function(String languageTag) onLocaleChanged;
|
||||
final Future<void> Function(ProfileSettingsV1 settings) onSettingsChanged;
|
||||
final Future<ProfileSettingsV1> Function(ProfileSettingsV1 updated)
|
||||
onSaveProfile;
|
||||
onSaveProfile;
|
||||
final Future<ProfileSettingsV1> Function(String filePath) onUploadAvatar;
|
||||
final Future<void> Function() onLogout;
|
||||
final Future<void> Function() onDeleteAccount;
|
||||
final void Function(int newBalance) onBalanceChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsScreen(
|
||||
account: account,
|
||||
userId: userId,
|
||||
settings: settings,
|
||||
coinBalance: coinBalance,
|
||||
inviteRepository: inviteRepository,
|
||||
@@ -600,6 +611,7 @@ class _ProfileTab extends StatelessWidget {
|
||||
onUploadAvatar: onUploadAvatar,
|
||||
onLogout: onLogout,
|
||||
onDeleteAccount: onDeleteAccount,
|
||||
onBalanceChanged: onBalanceChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -703,14 +715,23 @@ class _WelcomeDialog extends StatefulWidget {
|
||||
class _WelcomeDialogState extends State<_WelcomeDialog> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
bool _hasScrolledToBottom = false;
|
||||
bool _hasCheckedInitialScroll = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_handleScroll);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_syncScrollState();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (!_hasCheckedInitialScroll) {
|
||||
_hasCheckedInitialScroll = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_syncScrollState();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -730,7 +751,7 @@ class _WelcomeDialogState extends State<_WelcomeDialog> {
|
||||
}
|
||||
final max = _scrollController.position.maxScrollExtent;
|
||||
final current = _scrollController.offset;
|
||||
final canReadAll = max <= AppSpacing.xs || current >= max - AppSpacing.md;
|
||||
final canReadAll = max <= 50.0 || current >= max - AppSpacing.md;
|
||||
if (_hasScrolledToBottom == canReadAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user