feat: 新人初始礼包购买追踪功能
- 数据库:添加 has_purchased_starter_pack 字段到 register_bonus_claims - 后端:创建静态配置管理套餐信息,支持按国家/地区区分 - 后端:新增 GET /api/v1/points/packages API 返回可用套餐 - 后端:创建 utils/paths.py 统一路径管理 - 前端:动态获取套餐信息,移除硬编码 - 前端:添加 ProductCode 枚举约束,前后端类型安全 - 配置:Profile 默认国家改为 US(ISO 3166-1 alpha-2) - 文档:更新协议文档说明新 API 和字段
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
enum ProductCode { newUserPack, basicPack, popularPack, premiumPack }
|
||||
|
||||
enum PackageType { starter, regular }
|
||||
|
||||
class PackageInfo {
|
||||
const PackageInfo({
|
||||
required this.productCode,
|
||||
required this.type,
|
||||
required this.price,
|
||||
required this.credits,
|
||||
required this.isStarter,
|
||||
required this.starterEligible,
|
||||
required this.sortOrder,
|
||||
});
|
||||
|
||||
final ProductCode productCode;
|
||||
final PackageType type;
|
||||
final double price;
|
||||
final int credits;
|
||||
final bool isStarter;
|
||||
final bool starterEligible;
|
||||
final int sortOrder;
|
||||
|
||||
factory PackageInfo.fromJson(Map<String, dynamic> json) {
|
||||
return PackageInfo(
|
||||
productCode: _parseProductCode(json['productCode'] as String),
|
||||
type: json['type'] == 'starter'
|
||||
? PackageType.starter
|
||||
: PackageType.regular,
|
||||
price: (json['price'] as num).toDouble(),
|
||||
credits: json['credits'] as int,
|
||||
isStarter: json['isStarter'] as bool,
|
||||
starterEligible: json['starterEligible'] as bool,
|
||||
sortOrder: json['sortOrder'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
static ProductCode _parseProductCode(String code) {
|
||||
return switch (code) {
|
||||
'new_user_pack' => ProductCode.newUserPack,
|
||||
'basic_pack' => ProductCode.basicPack,
|
||||
'popular_pack' => ProductCode.popularPack,
|
||||
'premium_pack' => ProductCode.premiumPack,
|
||||
_ => throw ArgumentError('Unknown product code: $code'),
|
||||
};
|
||||
}
|
||||
|
||||
String get priceDisplay => '\$${price.toStringAsFixed(2)}';
|
||||
}
|
||||
|
||||
class PackagesResult {
|
||||
const PackagesResult({
|
||||
required this.region,
|
||||
required this.currency,
|
||||
required this.packages,
|
||||
});
|
||||
|
||||
final String region;
|
||||
final String currency;
|
||||
final List<PackageInfo> packages;
|
||||
|
||||
factory PackagesResult.fromJson(Map<String, dynamic> json) {
|
||||
return PackagesResult(
|
||||
region: json['region'] as String,
|
||||
currency: json['currency'] as String,
|
||||
packages: (json['packages'] as List<dynamic>)
|
||||
.map((e) => PackageInfo.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user