新增:三方支付完整调用
This commit is contained in:
parent
ea14c36d63
commit
066d8b7391
@ -88,6 +88,21 @@ abstract final class PaymentApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取订单详情(用于三方支付 webview 关闭后轮询)
|
||||||
|
static Future<ApiResponse> getOrderDetail({
|
||||||
|
required String asset,
|
||||||
|
required String federation,
|
||||||
|
}) async {
|
||||||
|
return _client.request(
|
||||||
|
path: '/v1/payment/getOrderDetail',
|
||||||
|
method: 'GET',
|
||||||
|
queryParams: {
|
||||||
|
'asset': asset,
|
||||||
|
'federation': federation,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Google 支付结果回调。body 为 sample(signature)、merchant(purchaseData)、federation(id)、asset(userId),见 docs/googlepay.md。
|
/// Google 支付结果回调。body 为 sample(signature)、merchant(purchaseData)、federation(id)、asset(userId),见 docs/googlepay.md。
|
||||||
static Future<ApiResponse> googlepay({
|
static Future<ApiResponse> googlepay({
|
||||||
required String sample,
|
required String sample,
|
||||||
|
|||||||
@ -80,7 +80,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
icon: LucideIcons.chevron_right,
|
icon: LucideIcons.chevron_right,
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () => Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => PaymentWebViewScreen(
|
builder: (_) => const PaymentWebViewScreen(
|
||||||
paymentUrl: 'http://www.petsheroai.xyz/privacy.html',
|
paymentUrl: 'http://www.petsheroai.xyz/privacy.html',
|
||||||
title: 'Privacy Policy',
|
title: 'Privacy Policy',
|
||||||
),
|
),
|
||||||
@ -92,7 +92,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
icon: LucideIcons.chevron_right,
|
icon: LucideIcons.chevron_right,
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () => Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => PaymentWebViewScreen(
|
builder: (_) => const PaymentWebViewScreen(
|
||||||
paymentUrl: 'http://www.petsheroai.xyz/terms.html',
|
paymentUrl: 'http://www.petsheroai.xyz/terms.html',
|
||||||
title: 'User Agreement',
|
title: 'User Agreement',
|
||||||
),
|
),
|
||||||
@ -143,18 +143,18 @@ class _ProfileHeader extends StatelessWidget {
|
|||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
imageUrl: avatarUrl!,
|
imageUrl: avatarUrl!,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (_, __) => Icon(
|
placeholder: (_, __) => const Icon(
|
||||||
LucideIcons.user,
|
LucideIcons.user,
|
||||||
size: 40,
|
size: 40,
|
||||||
color: AppColors.textSecondary,
|
color: AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
errorWidget: (_, __, ___) => Icon(
|
errorWidget: (_, __, ___) => const Icon(
|
||||||
LucideIcons.user,
|
LucideIcons.user,
|
||||||
size: 40,
|
size: 40,
|
||||||
color: AppColors.textSecondary,
|
color: AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Icon(
|
: const Icon(
|
||||||
LucideIcons.user,
|
LucideIcons.user,
|
||||||
size: 40,
|
size: 40,
|
||||||
color: AppColors.textSecondary,
|
color: AppColors.textSecondary,
|
||||||
@ -162,7 +162,7 @@ class _ProfileHeader extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: AppSpacing.lg),
|
const SizedBox(height: AppSpacing.lg),
|
||||||
Text(
|
Text(
|
||||||
userName ?? 'Guest',
|
userName ?? 'VIP',
|
||||||
style: AppTypography.bodyLarge.copyWith(
|
style: AppTypography.bodyLarge.copyWith(
|
||||||
color: AppColors.textPrimary,
|
color: AppColors.textPrimary,
|
||||||
),
|
),
|
||||||
@ -210,11 +210,11 @@ class _BalanceCard extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.surface,
|
color: AppColors.surface,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: const [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: AppColors.shadowLight,
|
color: AppColors.shadowLight,
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 2),
|
offset: Offset(0, 2),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -314,11 +314,11 @@ class _MenuItem extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.surface,
|
color: AppColors.surface,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
boxShadow: [
|
boxShadow: const [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: AppColors.shadowLight,
|
color: AppColors.shadowLight,
|
||||||
blurRadius: 6,
|
blurRadius: 6,
|
||||||
offset: const Offset(0, 2),
|
offset: Offset(0, 2),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -275,6 +275,9 @@ class _RechargeScreenState extends State<RechargeScreen>
|
|||||||
builder: (_) => PaymentWebViewScreen(paymentUrl: payUrl),
|
builder: (_) => PaymentWebViewScreen(paymentUrl: payUrl),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (mounted && orderId != null && orderId.isNotEmpty) {
|
||||||
|
_startOrderPolling(orderId: orderId, userId: userId);
|
||||||
|
}
|
||||||
_showSnackBar(
|
_showSnackBar(
|
||||||
context, 'Order created. Complete payment in the page.');
|
context, 'Order created. Complete payment in the page.');
|
||||||
AdjustEvents.trackPurchaseSuccess();
|
AdjustEvents.trackPurchaseSuccess();
|
||||||
@ -299,6 +302,42 @@ class _RechargeScreenState extends State<RechargeScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 三方支付 webview 关闭后轮询订单详情,间隔 1/3/7/15/31/63 秒;status 为 SUCCESS|FAILED|CANCELED 或订单不存在或轮询结束则停止;SUCCESS 时刷新用户信息
|
||||||
|
void _startOrderPolling({required String orderId, required String userId}) {
|
||||||
|
const delays = [1, 2, 4, 8, 16, 32]; // 累计 1,3,7,15,31,63 秒
|
||||||
|
Future<void> poll(int index) async {
|
||||||
|
if (index >= delays.length) return;
|
||||||
|
await Future<void>.delayed(Duration(seconds: delays[index]));
|
||||||
|
if (!mounted) return;
|
||||||
|
try {
|
||||||
|
final res = await PaymentApi.getOrderDetail(
|
||||||
|
asset: userId,
|
||||||
|
federation: orderId,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (!res.isSuccess || res.data == null) {
|
||||||
|
RechargeScreen._log.d('订单轮询 orderId=$orderId 订单不存在或请求失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final data = res.data as Map<String, dynamic>?;
|
||||||
|
final status = data?['line']?.toString().toUpperCase();
|
||||||
|
RechargeScreen._log.d('订单轮询 orderId=$orderId status=$status');
|
||||||
|
if (status == 'SUCCESS' || status == 'FAILED' || status == 'CANCELED') {
|
||||||
|
if (status == 'SUCCESS') {
|
||||||
|
refreshAccount();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
poll(index + 1);
|
||||||
|
} catch (e) {
|
||||||
|
RechargeScreen._log.w('订单轮询异常 orderId=$orderId: $e');
|
||||||
|
poll(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
poll(0);
|
||||||
|
}
|
||||||
|
|
||||||
/// ceremony==GooglePay 或 resource==GOOGLEPAY 时走谷歌内购并上报
|
/// ceremony==GooglePay 或 resource==GOOGLEPAY 时走谷歌内购并上报
|
||||||
static bool _isGooglePay(String paymentMethod, String? subPaymentMethod) {
|
static bool _isGooglePay(String paymentMethod, String? subPaymentMethod) {
|
||||||
final r = paymentMethod.trim().toLowerCase();
|
final r = paymentMethod.trim().toLowerCase();
|
||||||
@ -534,8 +573,8 @@ class _RechargeScreenState extends State<RechargeScreen>
|
|||||||
else
|
else
|
||||||
...List.generate(_activities.length, (i) {
|
...List.generate(_activities.length, (i) {
|
||||||
final item = _activities[i];
|
final item = _activities[i];
|
||||||
final isRecommended = false;
|
const isRecommended = false;
|
||||||
final isPopular = false;
|
const isPopular = false;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: i < _activities.length - 1 ? AppSpacing.xl : 0,
|
bottom: i < _activities.length - 1 ? AppSpacing.xl : 0,
|
||||||
@ -760,13 +799,13 @@ class _PaymentIcon extends StatelessWidget {
|
|||||||
? Image.network(
|
? Image.network(
|
||||||
iconUrl!,
|
iconUrl!,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
errorBuilder: (_, __, ___) => Icon(
|
errorBuilder: (_, __, ___) => const Icon(
|
||||||
LucideIcons.credit_card,
|
LucideIcons.credit_card,
|
||||||
size: 24,
|
size: 24,
|
||||||
color: AppColors.primary,
|
color: AppColors.primary,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Icon(
|
: const Icon(
|
||||||
LucideIcons.credit_card,
|
LucideIcons.credit_card,
|
||||||
size: 24,
|
size: 24,
|
||||||
color: AppColors.primary,
|
color: AppColors.primary,
|
||||||
@ -825,11 +864,11 @@ class _TierCardFromActivity extends StatelessWidget {
|
|||||||
color: AppColors.surface,
|
color: AppColors.surface,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(color: AppColors.border),
|
border: Border.all(color: AppColors.border),
|
||||||
boxShadow: [
|
boxShadow: const [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: AppColors.shadowLight,
|
color: AppColors.shadowLight,
|
||||||
blurRadius: 6,
|
blurRadius: 6,
|
||||||
offset: const Offset(0, 2),
|
offset: Offset(0, 2),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -962,7 +1001,7 @@ class _CreditsSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(LucideIcons.sparkles, size: 28, color: AppColors.surface),
|
const Icon(LucideIcons.sparkles, size: 28, color: AppColors.surface),
|
||||||
const SizedBox(width: AppSpacing.md),
|
const SizedBox(width: AppSpacing.md),
|
||||||
Text(
|
Text(
|
||||||
currentCredits,
|
currentCredits,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user