Browse Source

上传支付系统逻辑

weicu
liwei1dao 11 months ago
parent
commit
6f571d7a85
  1. 10
      lib/core/translations/language/en_us.dart
  2. 12
      lib/core/translations/language/zh_cn.dart
  3. 5
      lib/core/translations/language/zh_hk.dart
  4. 34
      lib/data/models/goods.dart
  5. 5
      lib/modules/goods/controllers/goods_base_controller.dart
  6. 41
      lib/modules/goods/utils/goods_i18n.dart
  7. 2
      lib/modules/goods/views/goodsai_view.dart
  8. 2
      lib/modules/goods/views/goodsmeet_view.dart
  9. 2
      lib/modules/goods/views/goodstrade_view.dart
  10. 38
      lib/modules/goods/views/goodsvip_view.dart
  11. 39
      lib/modules/pairing/controllers/pairing_controller.dart

10
lib/core/translations/language/en_us.dart

@ -599,6 +599,7 @@ const Map<String, String> enUS = {
// 设置页
"systemSettings": "System Settings",
"premiumMember": "Premium Membership",
"memberService": "Member Service",
"joinDevice": "Connected device:",
"accountId": "Account ID:",
"systemSetting": "System Settings",
@ -707,6 +708,15 @@ const Map<String, String> enUS = {
// 预设提示词
"presetMusic": "Play some music", // Play some music
"presetNavigation": "Navigation to", // Navigation
// First bind rewards dialog
"firstBindRewardTitle": "Welcome Reward",
"firstBindRewardDesc": "You've received binding rewards.",
"vipLevelLabel": "VIP Level:",
"expiryLabel": "Expiration:",
"aiChatCreditsLabel": "AI Chat Credits:",
"translationCreditsLabel": "Translation Credits:",
"meetingCreditsLabel": "Meeting Credits:",
"ok": "OK",
"presetWeather": "Today's weather", // Weather
"presetNews": "Today's news", // News
"presetFood": "Nearby food", // Food

12
lib/core/translations/language/zh_cn.dart

@ -162,7 +162,7 @@ const Map<String, String> zhCN = {
"exploreSlogan1": "智能陪伴,无限可能",
"exploreSlogan2": "智能眼镜,无限可能",
"exploreAi": "探索Voitrans提供的AI功能",
"aiFunction": "AI功能",
"aiFunction": "AI助手",
"allRoundAssistant": "全能助理",
"allRoundAssistantInfo": "帮你解决各类问题",
"simultaneousTranslation": "同声翻译",
@ -497,6 +497,7 @@ const Map<String, String> zhCN = {
// 设置页
"systemSettings": "系统设置",
"premiumMember": "高级会员",
"memberService": "会员服务",
"joinDevice": "已连接设备:",
"accountId": "账号ID:",
"systemSetting": "系统设置",
@ -691,6 +692,15 @@ const Map<String, String> zhCN = {
"friday": "周五",
"saturday": "周六",
"sunday": "周日",
// 首次绑定奖励弹窗(英文文案,保持一致性)
"firstBindRewardTitle": "Welcome Reward",
"firstBindRewardDesc": "You've received binding rewards.",
"vipLevelLabel": "VIP Level:",
"expiryLabel": "Expiration:",
"aiChatCreditsLabel": "AI Chat Credits:",
"translationCreditsLabel": "Translation Credits:",
"meetingCreditsLabel": "Meeting Credits:",
"ok": "OK",
// 天气描述
"sunny": "晴",
"partlyCloudy": "少云",

5
lib/core/translations/language/zh_hk.dart

@ -161,7 +161,7 @@ const Map<String, String> zhHK = {
"exploreSlogan1": "智能陪伴,無限可能",
"exploreSlogan2": "智能眼鏡,無限可能",
"exploreAi": "探索Voitrans提供的AI功能",
"aiFunction": "AI功能",
"aiFunction": "AI助手",
"allRoundAssistant": "全能助理",
"allRoundAssistantInfo": "幫你解決各類問題",
"simultaneousTranslation": "同聲翻譯",
@ -829,7 +829,8 @@ const Map<String, String> zhHK = {
"guideNavInput": "Navigate to Shenzhen Airport",
"guideNavSend": "Xiaoyan will plan the route",
"guideNavEnd": "Navigation set—try other commands",
"guideMeetingStart": "Tell Xiaoyan: “Create a meeting reminder for 3pm tomorrow”",
"guideMeetingStart":
"Tell Xiaoyan: “Create a meeting reminder for 3pm tomorrow”",
"guideMeetingInput": "Create a meeting reminder for 3pm tomorrow",
"guideMeetingSend": "Xiaoyan will create a meeting reminder",
"guideMeetingEnd": "Reminder created—try other commands",

34
lib/data/models/goods.dart

@ -1,6 +1,8 @@
/// 商品类型
/// 0: 普通产品 normal
/// 1: 会员产品 member
import 'dart:convert';
// 使用量类型(与后端/Proto枚举对应)
enum UsageType {
/// 未知使用量类型
@ -100,7 +102,7 @@ class DBGoods {
final String id;
final bool enable;
final String name;
final String desc;
final String localname;
final int price; // 建议单位:分
final UsageType usagetype;
final int usagenum;
@ -109,7 +111,7 @@ class DBGoods {
required this.id,
required this.enable,
required this.name,
required this.desc,
required this.localname,
required this.price,
required this.usagetype,
required this.usagenum,
@ -144,7 +146,7 @@ class DBGoods {
id: _asString(json['id']),
enable: _asBool(json['enable']),
name: _asString(json['name']),
desc: _asString(json['desc']),
localname: _asString(json['localname']),
price: _asInt(json['price']),
usagetype: UsageType.from(json['usagetype']),
usagenum: _asInt(json['usagenum']),
@ -157,7 +159,7 @@ class DBGoods {
'id': id,
'enable': enable,
'name': name,
'desc': desc,
'localname': localname,
'price': price,
'usagetype': usagetype.toInt,
'usagenum': usagenum,
@ -194,7 +196,7 @@ class DBGoods {
String? id,
bool? enable,
String? name,
String? desc,
String? localname,
int? price,
UsageType? usagetype,
int? usagenum,
@ -203,7 +205,7 @@ class DBGoods {
id: id ?? this.id,
enable: enable ?? this.enable,
name: name ?? this.name,
desc: desc ?? this.desc,
localname: localname ?? this.localname,
price: price ?? this.price,
usagetype: usagetype ?? this.usagetype,
usagenum: usagenum ?? this.usagenum,
@ -214,4 +216,24 @@ class DBGoods {
String toString() {
return 'DBGoods(id: $id, enable: $enable, name: $name, price: $price, usagetype: ${usagetype.name}, usagenum: $usagenum)';
}
Map<String, dynamic>? get _localnameMap {
if (localname.isEmpty) return null;
try {
final v = jsonDecode(localname);
if (v is Map<String, dynamic>) return v;
} catch (_) {}
return null;
}
Map<String, dynamic> localInfo(String tag) {
final m = _localnameMap;
if (m == null) return const {};
final key1 = tag;
final key2 = tag.replaceAll('_', '-');
final key3 = tag.replaceAll('-', '_');
dynamic e = m[key1] ?? m[key2] ?? m[key3];
if (e == null && m.isNotEmpty) e = m.values.first;
return e is Map<String, dynamic> ? e as Map<String, dynamic> : const {};
}
}

5
lib/modules/goods/controllers/goods_base_controller.dart

@ -439,7 +439,6 @@ abstract class GoodsBaseController extends GetxController {
final paymentData = await _payService.createWechatPayOrder(
productId: item.id,
productName: item.name,
productDesc: item.desc,
amount: cents,
notifyUrl: '$serverUrl/api/home/pay_wechatnotify',
);
@ -471,7 +470,6 @@ abstract class GoodsBaseController extends GetxController {
final orderInfo = await _payService.createAlipayOrder(
productId: item.id,
productName: item.name,
productDesc: item.desc,
amount: cents,
notifyUrl: 'https://api.ideapsound.com/api/home/pay_alipaynotify',
);
@ -502,7 +500,6 @@ abstract class GoodsBaseController extends GetxController {
final orderId = await _payService.createAppleIapOrder(
productId: item.id,
productName: item.name,
productDesc: item.desc,
amount: item.price,
);
if (orderId == null || orderId.isEmpty) {
@ -560,7 +557,6 @@ abstract class GoodsBaseController extends GetxController {
final orderId = await _payService.createGoogleIapOrder(
productId: item.id,
productName: item.name,
productDesc: item.desc,
amount: item.price,
);
if (orderId == null || orderId.isEmpty) {
@ -800,7 +796,6 @@ abstract class GoodsBaseController extends GetxController {
final approvalUrl = await _payService.createPaypalOrder(
productId: item.id,
productName: item.name,
productDesc: item.desc,
amount: cents,
notifyUrl: 'https://api.ideapsound.com/api/home/pay_paypalnotify',
);

41
lib/modules/goods/utils/goods_i18n.dart

@ -1,11 +1,20 @@
import 'package:get/get.dart';
import 'package:voitrans/data/models/goods.dart';
/// 商品名称本地化
/// 优先根据商品ID映射到本地化名称;若无映射,则回退到后端提供的名称
String localizedGoodsName(DBGoods item) {
final id = (item.id ?? '').trim();
String _localeTag() {
final l = Get.locale;
if (l == null) return 'zh-CN';
final cc = l.countryCode;
return cc != null && cc.isNotEmpty
? '${l.languageCode}-${cc.toUpperCase()}'
: l.languageCode;
}
String localizedGoodsName(DBGoods item) {
final info = item.localInfo(_localeTag());
final ln = (info['name'] as String?)?.trim() ?? '';
if (ln.isNotEmpty) return ln;
final id = item.id.trim();
switch (id) {
case 'ai_001':
return 'ai_001_name'.tr;
@ -25,17 +34,14 @@ String localizedGoodsName(DBGoods item) {
return 'trade_002_name'.tr;
case 'trade_003':
return 'trade_003_name'.tr;
// 可在此补充其他商品ID的映射,如:translate_001、meet_001 等
default:
final name = (item.name ?? '').trim();
final name = item.name.trim();
return name.isNotEmpty ? name : 'unknown'.tr;
}
}
/// 商品描述本地化
/// 优先根据商品ID映射到本地化描述;若无映射,则回退到后端提供的描述
String localizedGoodsDesc(DBGoods item) {
final id = (item.id ?? '').trim();
final id = item.id.trim();
switch (id) {
case 'ai_001':
@ -57,7 +63,20 @@ String localizedGoodsDesc(DBGoods item) {
case 'trade_003':
return 'trade_003_desc'.tr;
default:
final desc = (item.desc ?? '').trim();
return desc.isNotEmpty ? desc : '';
return '';
}
}
String goodsDisplayPrice(DBGoods item) {
final info = item.localInfo(_localeTag());
final cur = (info['currency'] as String?)?.trim();
final p = info['price'];
final symbol = (cur != null && cur.isNotEmpty) ? cur : '¥';
double v;
if (p is num) {
v = p.toDouble();
} else {
v = item.priceYuan;
}
return '$symbol${v.toStringAsFixed(2)}';
}

2
lib/modules/goods/views/goodsai_view.dart

@ -496,7 +496,7 @@ class GoodsaiView extends GetView<GoodsaiController> {
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('¥${item.priceYuan.toStringAsFixed(2)}',
Text(goodsDisplayPrice(item),
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,

2
lib/modules/goods/views/goodsmeet_view.dart

@ -260,7 +260,7 @@ class GoodsmeetView extends GetView<GoodsmeetController> {
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('¥${item.price.toStringAsFixed(2)}',
Text(goodsDisplayPrice(item),
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,

2
lib/modules/goods/views/goodstrade_view.dart

@ -260,7 +260,7 @@ class GoodstradeView extends GetView<GoodstradeController> {
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('¥${item.price.toStringAsFixed(2)}',
Text(goodsDisplayPrice(item),
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,

38
lib/modules/goods/views/goodsvip_view.dart

@ -24,7 +24,7 @@ class GoodsvipView extends GetView<GoodsvipController> {
onPressed: () => Get.back(),
),
title: Text(
'购买',
'purchase'.tr,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
@ -261,7 +261,7 @@ class GoodsvipView extends GetView<GoodsvipController> {
UsageType.AI: 'aiFunction'.tr,
UsageType.Meet: 'conferenceAssistant'.tr,
UsageType.Translation: 'simultaneousTranslation'.tr,
UsageType.Vip: 'premiumMember'.tr,
UsageType.Vip: 'memberService'.tr,
};
final iconAndColor = _iconColorForType(type, isDarkMode);
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
@ -281,8 +281,8 @@ class GoodsvipView extends GetView<GoodsvipController> {
? DateTime.fromMillisecondsSinceEpoch(vipexptime)
: DateTime.fromMillisecondsSinceEpoch(vipexptime * 1000);
}
titleStr = 'Vip $viplv';
vipSubtitle = expiry != null ? _fmtDate(expiry) : '未开启';
// titleStr = 'Vip $viplv';
vipSubtitle = expiry != null ? _fmtDate(expiry) : '未激活';
}
return Container(
padding: EdgeInsets.all(16.w),
@ -346,7 +346,7 @@ class GoodsvipView extends GetView<GoodsvipController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTypeHeader(type, isDarkMode),
SizedBox(height: 12.h),
SizedBox(height: 8.h),
_buildGoodsGrid(list, type, isDarkMode),
],
);
@ -504,7 +504,7 @@ class GoodsvipView extends GetView<GoodsvipController> {
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('¥${item.priceYuan.toStringAsFixed(2)}',
Text(goodsDisplayPrice(item),
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
@ -718,9 +718,9 @@ class GoodsvipView extends GetView<GoodsvipController> {
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12.h,
mainAxisSpacing: 8.h,
crossAxisSpacing: 12.w,
childAspectRatio: 1.15,
childAspectRatio: 1.3,
),
itemCount: list.length,
itemBuilder: (_, i) => _buildGridItemCard(list[i], type, isDarkMode),
@ -751,7 +751,7 @@ class GoodsvipView extends GetView<GoodsvipController> {
children: [
Container(
width: 34.w,
height: 34.w,
height: 20.w,
decoration: BoxDecoration(
color: iconColors.background,
borderRadius: BorderRadius.circular(17.r),
@ -761,11 +761,11 @@ class GoodsvipView extends GetView<GoodsvipController> {
SizedBox(width: 8.w),
Expanded(
child: Text(
localizedGoodsName(item),
goodsDisplayPrice(item),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14.sp,
fontSize: 21.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
@ -776,27 +776,19 @@ class GoodsvipView extends GetView<GoodsvipController> {
SizedBox(height: 6.h),
Expanded(
child: Text(
localizedGoodsDesc(item),
localizedGoodsName(item),
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12.sp,
fontSize: 15.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
),
SizedBox(height: 6.h),
// SizedBox(height: 6.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
'¥${item.priceYuan.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
GestureDetector(
onTap: () => controller.purchase(item),
child: Container(

39
lib/modules/pairing/controllers/pairing_controller.dart

@ -1073,6 +1073,45 @@ class PairingController extends GetxController with WidgetsBindingObserver {
User.instance.tradetotalintegral = response['tradetotalintegral'] ?? 0;
User.instance.meetintegral = response['meetintegral'] ?? 0;
User.instance.meettotalintegral = response['meettotalintegral'] ?? 0;
final lv = User.instance.viplv;
final vipexptime = User.instance.vipexptime;
DateTime? expiry;
if (vipexptime > 0) {
expiry = DateTime.fromMillisecondsSinceEpoch(vipexptime * 1000);
}
final y = expiry?.year.toString().padLeft(4, '0') ?? '';
final m = expiry?.month.toString().padLeft(2, '0') ?? '';
final d = expiry?.day.toString().padLeft(2, '0') ?? '';
final expiryText = expiry != null ? '$y-$m-$d' : 'N/A';
Get.dialog(
AlertDialog(
title: Text('firstBindRewardTitle'.tr),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('firstBindRewardDesc'.tr),
SizedBox(height: 8.h),
Text('${'vipLevelLabel'.tr} Vip $lv'),
Text('${'expiryLabel'.tr} $expiryText'),
SizedBox(height: 8.h),
Text(
'${'aiChatCreditsLabel'.tr} ${User.instance.aichatintegral}'),
Text(
'${'translationCreditsLabel'.tr} ${User.instance.tradeintegral}'),
Text(
'${'meetingCreditsLabel'.tr} ${User.instance.meetintegral}'),
],
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('ok'.tr),
),
],
),
barrierDismissible: true,
);
}
return;
} else {

Loading…
Cancel
Save