| 1 | import 'package:cake_wallet/core/utilities.dart'; |
| 2 | import 'package:cake_wallet/entities/fiat_currency.dart'; |
| 3 | |
| 4 | enum CakePayCardType { |
| 5 | prepaid('Prepaid Cards'), |
| 6 | gift('gift'), |
| 7 | onDemand('on_demand'), |
| 8 | custom('custom'); |
| 9 | |
| 10 | const CakePayCardType(this.apiValue); |
| 11 | |
| 12 | final String apiValue; |
| 13 | } |
| 14 | |
| 15 | class CakePayCard { |
| 16 | final int id; |
| 17 | final String name; |
| 18 | final CakePayCardType? type; |
| 19 | final String? description; |
| 20 | final String? termsAndConditions; |
| 21 | final String? howToUse; |
| 22 | final String? expiryAndValidity; |
| 23 | final String? cardImageUrl; |
| 24 | final String? country; |
| 25 | final FiatCurrency fiatCurrency; |
| 26 | final String? minValueUsd; |
| 27 | final String? maxValueUsd; |
| 28 | final String? minValue; |
| 29 | final String? maxValue; |
| 30 | final List<Denomination> denominationItems; |
| 31 | final List<PrepaidRange> prepaidRange; |
| 32 | |
| 33 | CakePayCard( |
| 34 | {required this.id, |
| 35 | required this.name, |
| 36 | this.type, |
| 37 | this.description, |
| 38 | this.termsAndConditions, |
| 39 | this.howToUse, |
| 40 | this.expiryAndValidity, |
| 41 | this.cardImageUrl, |
| 42 | this.country, |
| 43 | required this.fiatCurrency, |
| 44 | this.minValueUsd, |
| 45 | this.maxValueUsd, |
| 46 | this.minValue, |
| 47 | this.maxValue, |
| 48 | List<Denomination>? denominationItems, |
| 49 | List<PrepaidRange>? prepaidRange}) |
| 50 | : denominationItems = denominationItems ?? const [], |
| 51 | prepaidRange = prepaidRange ?? const []; |
| 52 | |
| 53 | CakePayCard copyWith({ |
| 54 | int? id, |
| 55 | String? name, |
| 56 | CakePayCardType? type, |
| 57 | String? description, |
| 58 | String? termsAndConditions, |
| 59 | String? howToUse, |
| 60 | String? expiryAndValidity, |
| 61 | String? cardImageUrl, |
| 62 | String? country, |
| 63 | FiatCurrency? fiatCurrency, |
| 64 | String? minValueUsd, |
| 65 | String? maxValueUsd, |
| 66 | String? minValue, |
| 67 | String? maxValue, |
| 68 | List<Denomination>? denominationItems, |
| 69 | List<PrepaidRange>? prepaidRange, |
| 70 | }) { |
| 71 | return CakePayCard( |
| 72 | id: id ?? this.id, |
| 73 | name: name ?? this.name, |
| 74 | type: type ?? this.type, |
| 75 | description: description ?? this.description, |
| 76 | termsAndConditions: termsAndConditions ?? this.termsAndConditions, |
| 77 | howToUse: howToUse ?? this.howToUse, |
| 78 | expiryAndValidity: expiryAndValidity ?? this.expiryAndValidity, |
| 79 | cardImageUrl: cardImageUrl ?? this.cardImageUrl, |
| 80 | country: country ?? this.country, |
| 81 | fiatCurrency: fiatCurrency ?? this.fiatCurrency, |
| 82 | minValueUsd: minValueUsd ?? this.minValueUsd, |
| 83 | maxValueUsd: maxValueUsd ?? this.maxValueUsd, |
| 84 | minValue: minValue ?? this.minValue, |
| 85 | maxValue: maxValue ?? this.maxValue, |
| 86 | denominationItems: denominationItems ?? this.denominationItems, |
| 87 | prepaidRange: prepaidRange ?? this.prepaidRange, |
| 88 | ); |
| 89 | } |
| 90 | |
| 91 | static CakePayCardType? cakePayCardTypeFromApi(String? value) { |
| 92 | if (value == null) return null; |
| 93 | final normalized = value.trim().toLowerCase(); |
| 94 | return CakePayCardType.values.firstWhereOrNull( |
| 95 | (e) => e.apiValue.toLowerCase() == normalized, |
| 96 | ); |
| 97 | } |
| 98 | |
| 99 | factory CakePayCard.fromJson(Map<String, dynamic> json) { |
| 100 | final name = stripHtmlIfNeeded(json['name'] as String? ?? ''); |
| 101 | final typeString = json['type'] as String? ?? ''; |
| 102 | final description = stripHtmlIfNeeded(json['description'] as String? ?? ''); |
| 103 | final termsAndConditions = stripHtmlIfNeeded(json['terms_and_conditions'] as String? ?? ''); |
| 104 | final howToUse = stripHtmlIfNeeded(json['how_to_use'] as String? ?? ''); |
| 105 | final fiatCurrency = FiatCurrency.deserialize(raw: json['currency_code'] as String? ?? ''); |
| 106 | |
| 107 | String? minValue = json['min_value'] as String?; |
| 108 | |
| 109 | final parsedMinValueLocal = _toDouble(json['min_value']); |
| 110 | final parsedMinValueUsd = _toDouble(json['min_value_usd']); |
| 111 | |
| 112 | if (parsedMinValueLocal != null && |
| 113 | parsedMinValueLocal > 0 && |
| 114 | parsedMinValueUsd != null && |
| 115 | parsedMinValueUsd > 0 && |
| 116 | parsedMinValueUsd < 10.0) { |
| 117 | final rate = parsedMinValueLocal / parsedMinValueUsd; |
| 118 | final minLocalValueLimit = 10.0 * rate; |
| 119 | minValue = minLocalValueLimit.toStringAsFixed(2); |
| 120 | } |
| 121 | |
| 122 | final raw = (json['denominations'] as List?) ?? const []; |
| 123 | final denominations = <Denomination>[]; |
| 124 | for (final item in raw) { |
| 125 | if (item is Map) { |
| 126 | denominations.add(Denomination.fromJson(Map<String, dynamic>.from(item))); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | final cakePayCardType = cakePayCardTypeFromApi(typeString); |
| 131 | |
| 132 | return CakePayCard( |
| 133 | id: json['id'] as int? ?? 0, |
| 134 | name: name, |
| 135 | type: cakePayCardType, |
| 136 | description: description, |
| 137 | termsAndConditions: termsAndConditions, |
| 138 | howToUse: howToUse, |
| 139 | expiryAndValidity: json['expiry_and_validity'] as String?, |
| 140 | cardImageUrl: json['card_image_url'] as String?, |
| 141 | country: json['country'] as String?, |
| 142 | fiatCurrency: fiatCurrency, |
| 143 | minValueUsd: json['min_value_usd'] as String?, |
| 144 | maxValueUsd: json['max_value_usd'] as String?, |
| 145 | minValue: minValue, |
| 146 | maxValue: json['max_value'] as String?, |
| 147 | denominationItems: denominations, |
| 148 | ); |
| 149 | } |
| 150 | |
| 151 | static String stripHtmlIfNeeded(String text) { |
| 152 | return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' '); |
| 153 | } |
| 154 | |
| 155 | static double? _toDouble(dynamic v) { |
| 156 | if (v == null) return null; |
| 157 | if (v is num) return v.toDouble(); |
| 158 | return double.tryParse(v.toString()); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | class Denomination { |
| 163 | final double value; |
| 164 | final int? cardId; |
| 165 | final double? usdValue; |
| 166 | |
| 167 | Denomination({ |
| 168 | required this.value, |
| 169 | required this.cardId, |
| 170 | this.usdValue, |
| 171 | }); |
| 172 | |
| 173 | factory Denomination.fromJson(Map<String, dynamic> json) { |
| 174 | return Denomination( |
| 175 | value: _toDouble(json['value']) ?? 0, |
| 176 | cardId: json['card_id'] is int ? json['card_id'] as int : int.tryParse('${json['card_id']}'), |
| 177 | usdValue: _toDouble(json['usd_value']), |
| 178 | ); |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | class PrepaidRange { |
| 183 | final double minValue; |
| 184 | final double maxValue; |
| 185 | final String rangeId; |
| 186 | |
| 187 | PrepaidRange({ |
| 188 | required this.minValue, |
| 189 | required this.maxValue, |
| 190 | required this.rangeId, |
| 191 | }); |
| 192 | |
| 193 | factory PrepaidRange.fromCard(CakePayCard card) { |
| 194 | return PrepaidRange( |
| 195 | minValue: _toDouble(card.minValue) ?? 0, |
| 196 | maxValue: _toDouble(card.maxValue) ?? 0, |
| 197 | rangeId: card.id.toString(), |
| 198 | ); |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | double? _toDouble(dynamic v) { |
| 203 | if (v == null) return null; |
| 204 | if (v is num) return v.toDouble(); |
| 205 | return double.tryParse(v.toString()); |
| 206 | } |