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:
@@ -7,19 +7,22 @@ import '../../../../shared/widgets/app_modal_dialog.dart';
|
||||
import '../../../../shared/widgets/gua_icon.dart';
|
||||
import '../../data/models/profile_settings.dart';
|
||||
import '../../data/repositories/invite_repository.dart';
|
||||
import '../models/legal_document_type.dart';
|
||||
import '../utils/legal_document_assets.dart';
|
||||
import 'account_delete_screen.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
import 'coin_center_screen.dart';
|
||||
import 'feedback_screen.dart';
|
||||
import 'general_settings_screen.dart';
|
||||
import 'invite_screen.dart';
|
||||
import 'legal_center_screen.dart';
|
||||
import 'legal_document_screen.dart';
|
||||
import 'profile_edit_screen.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
const SettingsScreen({
|
||||
super.key,
|
||||
required this.account,
|
||||
required this.userId,
|
||||
required this.settings,
|
||||
required this.coinBalance,
|
||||
required this.inviteRepository,
|
||||
@@ -30,9 +33,11 @@ class SettingsScreen extends StatefulWidget {
|
||||
required this.onLogout,
|
||||
required this.onDeleteAccount,
|
||||
required this.onSaveProfile,
|
||||
required this.onBalanceChanged,
|
||||
});
|
||||
|
||||
final String account;
|
||||
final String userId;
|
||||
final ProfileSettingsV1 settings;
|
||||
final int coinBalance;
|
||||
final InviteRepository inviteRepository;
|
||||
@@ -43,7 +48,8 @@ class SettingsScreen extends StatefulWidget {
|
||||
final Future<void> Function() onLogout;
|
||||
final Future<void> Function() onDeleteAccount;
|
||||
final Future<ProfileSettingsV1> Function(ProfileSettingsV1 updated)
|
||||
onSaveProfile;
|
||||
onSaveProfile;
|
||||
final void Function(int newBalance) onBalanceChanged;
|
||||
|
||||
@override
|
||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||
@@ -119,35 +125,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: _openInvite,
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.description_outlined,
|
||||
title: l10n.settingsLegalCenterTitle,
|
||||
tint: colors.secondary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: false,
|
||||
onTap: _openLegalCenter,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.feedback_outlined,
|
||||
title: l10n.settingsFeedbackTitle,
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: false,
|
||||
onTap: () => Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => FeedbackScreen(apiClient: widget.apiClient),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.person_rounded,
|
||||
title: l10n.settingsAccountAndDataTitle,
|
||||
@@ -159,6 +147,33 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.info_outline_rounded,
|
||||
title: l10n.aboutUs,
|
||||
tint: colors.secondary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: () => _openLegalDocument(LegalDocumentType.aboutUs),
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.security_rounded,
|
||||
title: l10n.privacyPolicy,
|
||||
tint: colors.secondary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: () => _openLegalDocument(LegalDocumentType.privacyPolicy),
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.description_outlined,
|
||||
title: l10n.termsOfService,
|
||||
tint: colors.secondary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: false,
|
||||
onTap: () => _openLegalDocument(LegalDocumentType.termsOfService),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
FilledButton(
|
||||
onPressed: _confirmLogout,
|
||||
style: FilledButton.styleFrom(
|
||||
@@ -180,7 +195,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Future<void> _openCoinCenter() async {
|
||||
await Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => CoinCenterScreen(balance: widget.coinBalance),
|
||||
builder: (_) => CoinCenterScreen(
|
||||
balance: widget.coinBalance,
|
||||
userId: widget.userId,
|
||||
onBalanceChanged: widget.onBalanceChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -229,9 +248,16 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _openLegalCenter() async {
|
||||
await Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(builder: (_) => const LegalCenterScreen()),
|
||||
void _openLegalDocument(LegalDocumentType type) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final locale = Localizations.localeOf(context);
|
||||
Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => LegalDocumentScreen(
|
||||
title: legalDocumentTitle(l10n, type),
|
||||
assetPath: legalDocumentAssetPath(locale, type),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user