新增:三方支付完整调用

This commit is contained in:
ivan 2026-03-15 11:25:09 +08:00
parent ea14c36d63
commit 066d8b7391
3 changed files with 71 additions and 17 deletions

View File

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

View File

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

View File

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